You started with a single rule:
"n": (n) => n A number evaluates to itself. That was the whole language.
From there you added operators, then variables, then functions. You hit a bug where variables clobbered each other and fixed it by giving each function its own environment. That fix had a name: closures.
You used closures to build data. A pair is just a function that remembers two values and lets the caller choose. A list is a chain of pairs ending in nil -- a type you invented because you needed a way to say "nothing here."
You wrote syntactic sugar: twelve one-line functions that changed nothing about the language but made it readable. And in the final problem, you built an interpreter inside your interpreter, then watched the same expression produce a number, a string, and a count -- three meanings from one tree.
Along the way, a pattern kept appearing: tagged data plus dispatch. Your evaluator checks a tag (number? string? array with "if"?) and calls the matching rule. Your pairs use a selector to choose between two values. Your expression trees use a tag to choose between lit, add, and mul. It is the same idea every time. Data carries a label. Code reads the label and decides what to do.
This pattern has many names in other contexts -- pattern matching, dynamic dispatch, polymorphism, the visitor pattern. You have been using it since step one.
What you built is small. It fits in a page. But the ideas in it are the same ideas that run real languages. Lexical scoping, closures, environments, tagged data, recursive evaluation -- these are not simplifications. They are the actual mechanisms.
The difference between your language and a production one is not in the ideas. It is in the engineering: parsing, optimization, error messages, tooling, and thousands of edge cases. The foundation is what you just built.