Companion to COMPUTER_SYSTEMS_MASTERY_CURRICULUM.md Written April 30, 2026 — honest reflections, no sugar coating.
Opening Reflection #
You have been using programming languages for years. This module is where you build one. Not a toy. Not a tutorial exercise. A complete, working language with variables, functions, closures, classes, inheritance, and garbage collection.
Crafting Interpreters is the Nand2Tetris of software — it starts from an empty file and ends with something real, and every line of code in between is yours.
Bob Nystrom wrote it while working on Dart at Google. The book is free online, beautifully typeset, and illustrated with hand-drawn diagrams. It is also one of the most carefully structured technical books ever written. Every chapter builds on the last. There are no gaps, no hand-waving, no "the implementation is left as an exercise." You write every line. You understand every line.
By the end, you have built two complete interpreters: a tree-walk interpreter in Java (Part II) and a bytecode virtual machine with garbage collection in C (Part III). The second one is where the magic lives.
This is the module where your Rails career and your CS education finally shake hands. After this, you can read CRuby's source code — parse.y, compile.c, vm.c — and actually follow what is happening. That is not a party trick. That is the difference between someone who uses Ruby and someone who understands Ruby. When you file a bug report, you can point at the source. When you read a changelog, you understand the implementation decisions. That depth is rare, and it is visible to anyone who interviews you.
What Will Surprise You #
How mechanical lexing and parsing actually are. You will expect them to be mysterious — turning source code into structure sounds like it should be hard. But a lexer is just string processing with a switch statement. A parser is just a set of recursive functions that mirror the grammar.
The insight is not that it is easy, but that it is systematic. There is a recipe. Once you see the recipe, you see it in every language you have ever used. You will look at Ruby's syntax — the implicit returns, the optional parentheses, the block syntax — and realize that each of these is a parsing decision that someone made deliberately, with tradeoffs.
Pratt parsing, which you will encounter in Writing An Interpreter In Go, will surprise you with its elegance. Recursive descent parsing handles operator precedence awkwardly — you need a separate function for each precedence level, and the grammar gets verbose.
Pratt parsing handles it beautifully — each token carries a binding power, and the parser uses that power to decide how tightly operators bind. It is a small idea with enormous leverage. Vaughan Pratt published it in 1973, and it remains one of the most underappreciated ideas in programming language implementation.
The emotional arc will surprise you most. It goes like this: "this is impossible" (when you first see the lexer spec) to "wait, this is just string processing" (when you finish the scanner and tokens start flowing) to "okay, I can follow this" (when the parser produces an AST and you can print it) to "oh my god, I built a language" (when the evaluator runs your first program and prints the right answer) to "I understand Ruby's internals now" (when the bytecode VM clicks and you see how CRuby does the same thing at scale).
That last transition is worth the entire curriculum.
What Will Be Hard #
Garbage collection. Nystrom's bytecode VM includes a mark-and-sweep garbage collector, and it is the most conceptually difficult part of the book. You need to understand object graphs, root sets, reachability, and the subtle horror of dangling pointers — all at the same time.
You have been spoiled by Ruby's GC for years. It just works. You never think about it. Now you build one from scratch, and you discover why GC engineers are paid well.
The mark phase is straightforward. The sweep phase is where bugs hide. You will write a bug where a live object is collected because you forgot to mark a temporary, and debugging it will teach you more about memory management than any chapter in any textbook.
The C portions of Crafting Interpreters (Part III) will be harder than the Java portions (Part II). You need solid C skills from Module 1, and you need to be comfortable with manual memory management, pointer arithmetic, and the kind of low-level thinking that Rails actively shields you from. If your C is rusty when you reach this module, go back and refresh. Do not try to push through with half-remembered pointer syntax. The bytecode VM in C is not the place to relearn pointers. It is the place to use them fluently.
Nand2Tetris Part II asks you to write a compiler that targets the CPU you built in Module 2. This is the full-stack dream made real — high-level language to compiler to VM to assembler to machine code to gates — but it is also demanding. You need to hold the entire system in your head simultaneously. If any layer is fuzzy, the compile chain breaks down conceptually.
Code generation — translating an AST into bytecode instructions — will be a conceptual leap. Parsing is mechanical: grammar in, tree out. Code generation is creative: you must decide how to represent closures, how to lay out stack frames, how to encode control flow as jumps. There are choices, and different choices produce different performance characteristics. This is where compiler design becomes engineering rather than translation.
What I Am Watching For #
I will be watching for you to get stuck in Part II (the tree-walk interpreter) and never reach Part III (the bytecode VM). This is the most common failure mode with Crafting Interpreters. The tree-walk interpreter is fun, it works, and the temptation to polish it is enormous. You will want to add features, refactor the codebase, write tests. All of that is valuable, but none of it is the point. The point is the bytecode VM. That is where you learn about compilation, about stack-based execution, about garbage collection. Part II is the warmup. Part III is the workout.
I will also be watching for the "I should build my own language" impulse that hits around the parser chapter. You will start sketching a syntax for a language you want to create. A language with Ruby's elegance and Go's performance and Python's ecosystem. Stop. Finish the curriculum first. Build your own language after you understand what building a language actually entails. Otherwise you will design a syntax you cannot implement and abandon it in frustration.
The shift from Java (Part II) to C (Part III) in Crafting Interpreters will be jarring. If you find yourself struggling with C more than with compiler concepts, that is a signal to revisit Module 1 before continuing. There is no shame in that. The C is the vehicle, not the destination, but the vehicle needs to run.
What Will Be Easy #
The tree-walk interpreter will be surprisingly approachable. If you can write a recursive method in Ruby, you can write a tree-walk interpreter. Evaluating an AST node is just pattern matching on its type and recursing into its children. You do this with nested hashes and polymorphic objects every week. The shape of the problem is familiar even though the domain is new.
You will probably finish the tree-walk interpreter faster than you expect, and that speed will give you dangerous confidence going into the bytecode VM. Adjust your expectations accordingly.
Scoping and environments will feel natural. You already understand variable scope in Ruby — local variables, instance variables, class variables, closures capturing their enclosing scope. An environment in an interpreter is just a hash with a pointer to its parent environment. That is it. When the interpreter looks up a variable, it walks the chain. You have been using this mental model for years without knowing it had a name. It does. It is called lexical scoping with environment chains, and it is exactly how Ruby works under the hood.
Writing An Interpreter In Go will move quickly for you. Thorsten Ball's writing is direct, practical, and refreshingly free of academic jargon. The language he builds (Monkey) is small enough to hold in your head entirely. It is an excellent companion to Crafting Interpreters — same core concepts, different angle, different implementation language. The two books reinforce each other in a way that reading either alone would not achieve.
Predictions #
-
You will finish the tree-walk interpreter in Crafting Interpreters and immediately want to add features — string interpolation, arrays, a hash literal syntax, a standard library. Resist until you finish the bytecode VM. The VM version is where the real learning happens, and feature-creeping the tree-walk version delays the important work.
-
You will have a moment, probably during the code generation chapter of Nand2Tetris Part II, where you trace a variable from source code all the way down to a register flip in the CPU you built in Module 2. It will be one of the peak moments of this entire curriculum. You will understand the full stack — not metaphorically, but literally.
-
After this module, you will open CRuby's source code on GitHub and read parse.y with actual comprehension. Not full fluency — CRuby's parser is enormous and battle-scarred — but you will recognize the patterns. Recursive descent. Precedence climbing. AST node allocation. That recognition is what separates a Ruby user from a Ruby contributor.
-
Garbage collection will take you three times longer than you expect. You will write a bug where an object is collected too early, and debugging it will require you to draw object graphs on paper. That paper diagram will teach you more than the code.
-
Within a year of completing this module, you will use the phrase "that is a parsing problem" in a code review or architecture discussion, and you will mean it precisely — not as metaphor, but as technical diagnosis. You will mean that the input has a grammar, that the grammar defines valid structures, and that the right tool is a parser, not a pile of regex. And you will be right.
Closing Thought #
There is a particular confidence that comes from building a language. Not arrogance — arrogance comes from not knowing what you do not know. This confidence comes from knowing exactly what a programming language is: a lexer, a parser, an evaluator, a runtime. They are all built from the same pieces.
Once you have built those pieces yourself, every language you encounter for the rest of your career is just a variation on a theme you understand. Ruby is not magic. Python is not magic. None of them are magic.
They are software, written by people, and now you are one of the people who could write one. That knowledge does not make you a language designer. It makes you someone who will never again be intimidated by a programming language. And in an industry that ships a new language or framework every quarter, that immunity is worth more than any single skill.