Programming languages are fascinating. Learning how they work shouldn’t feel like fighting a dragon.
On Monday, August 3, 2026, I’m launching Tiny Interpreters, a new blog and newsletter about learning how programming languages work, one tiny interpeter at a time. We’ll begin by building the interpreters in Elm, one language feature at a time.
It’s for Elm developers, functional programmers, and anyone curious about programming languages who would rather begin with something small and understandable than confront an entire compiler at once.
Each interpreter will introduce one carefully chosen idea and follow it through the language’s design and implementation. We’ll build up our understanding gradually, allowing the deeper ideas to emerge from programs we can see, run, and reason about.
Tiny Interpreters is the path into programming languages I wish I could have followed when I first tried to learn the subject. It took me years—and one unsuccessful encounter with a dragon—to find that path for myself.
My first encounter with the dragon
I became interested in programming languages when I discovered that studying, designing, and building them brought together several fields I enjoyed: mathematics, computer science, and software development.
My university didn't offer a course in programming languages or compiler construction while I was there, so I had to explore the subject on my own.
A professor whose opinion I respected pointed me towards Compilers: Principles, Techniques, and Tools, better known as the Dragon Book—the textbook the university had used when it still offered the compilers course. I ordered a copy through the university bookstore. When it arrived, I eagerly began working through it, but I didn't get very far.
I learned a great deal about parsing and syntax-directed translation, but I still lacked a clear framework for understanding how programming language features were designed, implemented, and made to work together.
In hindsight, the Dragon Book is a good reference, but it wasn't the right book for me as a complete beginner studying on my own. An experienced professor could have selected the right material, provided context, and connected the theory to practical work. Without that guidance, I was four chapters in and had absorbed plenty of theory, but I still couldn't build the kinds of language features that had drawn me to the subject. Eventually, I lost the motivation to continue.
It would be a few more years before my dragon-inflicted wounds healed and I found a path into the subject that worked for me.
Finding another way in
In the Getting Started thread on Lambda the Ultimate, Paul Snively recommended four books he considered especially good starting points for learning programming language design. One of them was EOPL—Essentials of Programming Languages—which he suggested beginning with, so I started there.
EOPL was also challenging. I had to work through two introductory chapters and an appendix before reaching the material I had come for: the sequence of interpreters that begins in Chapter 3. Once I got there, however, the challenge felt purposeful rather than obstructive. The book allowed me to confront one meaningful problem at a time.
EOPL led me through a sequence of small languages, each implemented with an interpreter and extending the one before it. With each new feature, I could follow the change from the language’s concrete syntax, through its parser and abstract syntax, to the evaluator that gave it meaning—all without losing sight of how the whole language worked.
For the first time, programming language design and implementation felt like a process I could follow. I didn't have to understand an entire compiler before I could build something meaningful. I could begin with a small language, add one feature, and follow its consequences through the language’s design and implementation.
Then I started seeing these ideas everywhere
Building interpreters taught me more than the inner workings of programming languages. It changed the way I viewed software.
One idea in particular stood out: instead of doing a computation right away, a program can build a data structure that describes it. Another part of the system can then inspect, transform, or interpret that description. Once I saw this separation between description and execution, I started noticing it in Elm libraries and tools I already used.
I saw it in elm/json. Its decoder API lets us build a value that describes how JSON should be traversed, checked, and converted into an Elm value. The runtime later interprets that description while decoding a JSON value. What I had treated as an ordinary library API was also an embedded language for describing a decoding process.
I saw another side of it in the Elm compiler. In On continuation-passing style and the factorial function, I compared an ordinary recursive factorial function with a tail-recursive version that the compiler turns into a JavaScript loop. The compiler can make that transformation because it has a representation of the program that it can inspect and transform, altering how the computation runs without changing what it means.
I encountered a related idea while building elm-trs2, my Elm adaptation of the relational programming language from The Reasoned Schemer. The book supplied the core design; I translated and refined it for Elm. The result was an embedded language whose design raised familiar questions about representation, composition, and meaning.
Through those experiences, programming language theory stopped feeling confined to interpreters and compilers.
Learning how languages work was also teaching me new ways to write software.
The Tiny Interpreters approach
Tiny Interpreters will take the one-feature-at-a-time approach that worked for me and make the path even more gradual and approachable. We’ll introduce concepts slowly, explain them without assuming familiarity with the field’s academic terminology, and keep each interpreter small enough that the important ideas remain visible.
For each new language feature, we’ll begin by deciding what it should mean. That decision will guide everything that follows: how the feature is represented in the language’s concrete and abstract syntax, how it is parsed and evaluated, any new values or supporting data structures it requires, and the tests that show whether the implementation behaves as intended. We’ll follow the feature from idea to complete implementation without losing sight of how each decision affects the interpreter as a whole.
We’ll use Elm because it makes the structures inside an interpreter explicit without adding much unrelated complexity. Custom types let us model syntax, values, errors, and other parts of the language directly, while pattern matching makes it natural to write functions that follow those structures. Elm is small, pure, and call-by-value, and its elm/parser library gives us the tools to build complete interpreters. Together, these qualities help us keep our attention on the ideas we're trying to understand.
As you learn to build interpreters, you’ll also develop skills that apply to software design more broadly. You’ll learn to model ideas with precise data structures, design APIs that express what users can do, test implementations against their intended meaning, and trace how one decision affects an entire system. These are skills you can carry into parsers, libraries, web applications, and other kinds of software.
Come along for the journey
The first Tiny Interpreters article will be published next Monday, August 3, 2026. From there, we’ll gradually build up our understanding of programming languages, one tiny interpreter and one carefully chosen idea at a time.
If you’ve wanted to learn how programming languages work but haven’t found a path that feels approachable, I hope Tiny Interpreters can be that path for you.
Subscribe to the Tiny Interpreters newsletter, and I’ll send you the first article when it goes live.
Top comments (0)