A Python interpreter in 1024 bytes is a compiler course
Austin Henley squeezed a working Python interpreter into 1024 bytes of C. The interesting part isn't the golf — it's how small a real interpreter actually is.

Austin Z. Henley built a working Python interpreter in 1024 bytes of C, and published the whole thing at austinhenley.com/blog/python1024. It runs FizzBuzz. It handles recursion. It fits in less space than the average React component.
I want to argue that the byte count is the least interesting thing here. The real finding is buried underneath it: a language interpreter that most CS students treat as a final-year boss fight is, stripped of everything optional, about a page of code.
🔍 What actually fits in 1024 bytes
Henley set the limit first and worked backwards. His stated rule was no "macro shenanigans or library tomfoolery" — just GNU C89 and libc. He tried 512 bytes first and gave up on it.
Here is what survived the cut:
| Feature | Supported? |
|---|---|
| Integer variables (single lowercase letter) | ✅ |
Arithmetic + - * % with correct precedence |
✅ |
Comparisons < > <= >= == |
✅ |
if / else |
✅ |
while and for x in range(y), both with else |
✅ |
| Function definitions, calls, recursion | ✅ |
| Indentation-based blocks | ✅ |
print of a string literal or integer expression |
✅ |
| Comments, integer truthiness | ✅ |
| Multi-character variable names | ❌ |
| Function arguments | ❌ |
| Error handling | ❌ |
Comparison chaining (a < b < c) |
❌ |
That is not a toy calculator. Indentation-sensitive blocks plus recursion plus operator precedence is a genuine language. The internal state is almost comically small: a 999-byte fixed array holding the raw Python source, and a 256-entry symbol table for variables.
Key takeaway: The gap between "I can write a for loop" and "I can write the thing that runs a for loop" is far narrower than the university syllabus makes it look.
⚡ The trick is executing while you parse
The reason this fits at all is an architectural choice, not a golfing one. Henley uses a recursive descent parser that executes as it parses. There is no tokenizer stage handing off to a parser stage handing off to a bytecode compiler handing off to a VM.
That single decision deletes most of what a textbook interpreter carries:
- No token struct, no token array, no lexer loop
- No AST nodes, no allocation, no tree walker
- No intermediate representation, no bytecode, no dispatch table
- No separate pass boundaries to keep in sync
Compare the shapes:
| Stage | Textbook interpreter | This one |
|---|---|---|
| Lexing | Separate pass, produces tokens | Inline, character by character |
| Parsing | Builds an AST | Function call stack is the tree |
| Evaluation | Walks the AST or runs bytecode | Happens during the parse |
| Memory | Heap-allocated nodes | Fixed 999-byte buffer + 256 slots |
The call stack of the parser becomes the expression tree. parse_sum calls parse_term calls parse_factor, and precedence falls out of the nesting for free. If you have ever stared at a grammar and wondered where precedence "lives", this is the answer: it lives in which function calls which.
I've watched people bounce off compiler courses because they hit lexer generators and parser tables in week two and concluded the whole field is machinery. It isn't. The machinery exists to handle scale, error messages, and optimisation. The core idea is small enough to hold in your head.
🛠️ Code golf is a bad habit and a good exercise
The honest version of this project was over 4,800 bytes. Getting to 1024 meant a second, deliberately worse copy of the same program. Henley's techniques were the usual C golfing set:
- Single-letter identifiers everywhere
- Implicit
intdeclarations (legal in C89, gone in modern C) - Comparing raw ASCII values instead of character literals
- Ternaries and the comma operator instead of statements
- Bitwise
&and|standing in for&&and|| - Function parameters reused as scratch variables
His own verdict on the process was that it was "quite tedious", mostly because of shuttling between the readable version and the minified one. That is the part I'd underline for anyone tempted to copy the style.
Golfing is a fine way to learn what a language actually permits. It is a terrible way to write code anyone else has to maintain, including you in three weeks.
If you want to see how much of your own code is incidental rather than essential, run it through our HTML/CSS/JS minifier and look at the ratio. That's your ceiling for mechanical savings. Everything below it is a design change, which is what Henley actually did by picking single-pass execution.
🎓 Why this is a good weekend for a Sri Lankan CS student
Compiler and language modules at UCSC, Moratuwa, SLIIT and NSBM tend to be the ones students dread. Part of that is the material. A larger part, I think, is that the assignments start at industrial scale — write a lexer, write a parser, produce three-address code — before anyone has felt a language work.
A 1024-byte interpreter inverts that. You get the dopamine hit first.
Practical version of the exercise, on any hardware you already own:
- Zero setup cost. GCC or Clang, one
.cfile. No LLVM, no ANTLR, no 8GB of toolchain on a laptop that's already struggling. - Scope it to an afternoon. Integers, four operators,
print,if,while. That's it. Add functions on day two. - Test in the browser first. Write the Python programs you intend to support in our online Python compiler, confirm the behaviour you're copying, then make your C match it. Prototype the C side in the online C compiler if you don't want to set up a local toolchain yet.
- Ship it publicly. Henley's is on GitHub at AZHenley/python1024. A small, finished, working interpreter on your profile says more to a hiring manager than a half-built clone of something large.
That last point matters here more than it does elsewhere. Sri Lankan grads competing for remote roles are usually screened on a portfolio, not a transcript. "I wrote a language" is a five-minute interview story with a live demo attached.
💡 What this means for you
Henley's framing for the project was that he writes code by hand on weekends "to feel human". Fair enough. But the transferable lesson isn't about craft nostalgia.
- Hard constraints produce better design decisions than good intentions. The 1024-byte cap is what forced single-pass execution. A vague goal of "keep it simple" would not have.
- Most complexity in a system is optional. Error handling, multi-character identifiers and function arguments were the first things off the boat. Note that they're also the first things a real product needs, which tells you where the actual work in language design sits.
- Scope down until you finish. A 1024-byte interpreter that runs FizzBuzz beats a 40,000-line abandoned one.
If you've been putting off the compilers module, or you've had "write a small language" sitting in your notes for two years, this is the smallest possible on-ramp. One file. One weekend. No dependencies.
Original source
Making a Python interpreter in 1024 bytes