- interpretation of a variable (is it a list, a number, a string, a hash?) depends on the context in which it's used, and the context depends on, um, its context;
- pervasive global state (`$_` and friends);
- you can mess with compile-time operations, patch the language on the fly, e.g. during module imports;
- autovivification: a single read by key from a hash is enough to actually create this key; variables are a number and a string at once, but the number will not be computed until you need this variable in number context. Reading a non-existing key in a hash as a hash actually creates a new subhash.
- ties: you can override access to any variable by your methods;
- the built-in data structures are special, there is no sane way to emulate and extend them (but, as usual, for sure there are insane ways);
- the built-in `sort` takes a code block (not a closure with parameters), that has magic variables `$a` and `$b` appearing out of thin air. It looks like a closure, but it's not.
- features are often added in a convoluted way. E.g. there was a (now deprecated) feature that allowed `push` and `shift` to take a reference to a hash instead of a hash directly. How was it implemented internally? Basically by backpatching the error condition when the existing `push`/`shift` choked on a reference.
- the language design principle is "Do What I Mean" (where the "I" part varies). It's ironic that a language built "for human communication" by a linguist (what could possibly go wrong?) is harder to read and to reason about that programming languages built around logic.
On the other hand, if your business brings enough income to pay good developers with strong discipline, you can still have a big codebase in Perl and sustain it for years: it's better to have a terrible language and good engineers than bad engineers and a good language.