Peter Norvig Publishes Lisp Interpreter Tutorial in Python

Peter Norvig published "How to Write a (Lisp) Interpreter (in Python)" on 21 June 2026, walking through the construction of a working Scheme interpreter — dubbed Lispy — in Python. The tutorial covers lexing, parsing, and evaluation for a substantial subset of Lisp, stopping well short of a full R7RS implementation but hitting enough of the language to be instructive about interpreter design in general.
Norvig is co-author of Artificial Intelligence: A Modern Approach, the canonical undergraduate AI text, and spent years as Director of Research at Google. His tutorials at norvig.com carry weight precisely because they are written by someone who has built production systems at scale, not merely described them.
The tutorial's core pedagogical move is familiar to anyone who has taught language implementation: strip the language to its load-bearing members, get evaluation working, then layer complexity on top. Lispy handles the fundamental forms — define, lambda, if, quote, begin — along with arithmetic, comparison, and a standard environment seeded with Python's math module. The parser reduces to a recursive descent over token lists; the evaluator is a short eval function dispatching on expression type. The whole thing fits in under 100 lines of Python.
That terseness is the point. Long before LLVM toolchains and tree-sitter grammars became the default context for talking about language implementation, Lisp was the canonical vehicle for explaining how interpreters work — McCarthy's original eval in 1960, SICP's metacircular evaluator in the 1980s, and a long line of tutorial implementations since. Norvig's contribution sits squarely in that tradition, updating it for a Python-native audience that may never have written a line of Lisp.
A companion piece, "An ((Even Better) Lisp) Interpreter (in Python)", extends Lispy with tail-call optimization, proper tail recursion, a new data type, and macro support — the features that separate a toy from something you could plausibly use. The two articles together trace a common arc in language implementation work: get the semantics right first, then address the performance and ergonomics that real use demands.
Norvig also published the interpreter source directly as lis.py, a standalone Python file carrying a copyright notice spanning 2010–16, giving readers a complete, runnable artifact rather than just prose and snippets.
Worth flagging here: the choice of Lisp as the teaching vehicle is not arbitrary sentimentality. Lisp's homoiconicity — the property that code and data share the same representation — makes the evaluation loop unusually transparent. An eval for Lisp can be expressed recursively in a way that mirrors the language's own structure, which is why the metacircular evaluator has been a pedagogy staple for decades. Python's dynamic typing and first-class functions make it a natural host: you can represent a Lisp environment as a plain dict, a procedure as a Python closure, and an expression as a nested list, without fighting the type system at every turn.
For working engineers, the practical takeaway from Norvig's tutorial is less about Lisp specifically and more about what a minimal interpreter loop actually looks like. Anyone who has written a configuration DSL, a rules engine, or a query language on top of an existing runtime has, in some form, built a restricted evaluator. Seeing the full pipeline — tokenizer, recursive parser, environment-threaded eval — laid out in under a hundred lines is a useful calibration against the complexity that accumulates in production implementations.
The timing relative to renewed interest in domain-specific languages is incidental; Norvig's tutorial is structured as durable reference material rather than commentary on any particular moment in the industry. The longevity of lis.py, whose copyright predates the tutorial's current publication by over a decade, suggests the underlying code has been a quiet resource for some time.
For practitioners who want to go further, the path from Lispy to something production-adjacent runs through proper tail-call elimination, garbage collection strategy, and — if you want to target native code — a compiler backend. The second Norvig tutorial covers some of that ground. Beyond it, Christian Queinnec's Lisp in Small Pieces and the Nystrom Crafting Interpreters project offer the next levels of depth. Norvig's pair of articles earns its place at the start of that reading list.


