Making a Python interpreter in 1024 bytes

🔥 Explore this trending post from Hacker News 📖

📂 **Category**:

📌 **What You’ll Learn**:


Making a Python interpreter in 1024 bytes – Austin Z. Henley









Austin Z. Henley

I build tools for people



9/6/2026

A screenshot of the 1024 bytes of golfed C code.

To feel human, I write code by hand on the weekends.

My latest challenge? Make a Python interpreter in 512 1024 bytes of good ole C code. Oh, and no macro shenanigans or library tomfoolery.

def buzz():
    for n in range(101):
        if n % 15 == 0:
            print("FizzBuzz")
        else:
            if n % 3 == 0:
                print("Fizz")
            else:
                if n % 5 == 0:
                    print("Buzz")
                else:
                    print(n)
buzz()

I probably can’t fit all of the Python language into an interpreter that is only 1024 bytes of code. So what can I fit that will look like Python?

This fizzbuzz program looks distinctly Python. It has the def, the colons, the indentations, and no parentheses for if statements. Looks like Python to me! Of course, I’ll also have to add some additional limitations beyond just a subset of the syntax.

My first attempt was bad though.

First try: 512 bytes is not enough!

I’ve written many recursive descent parsers, so how different can this be? A subset of Python should be similar to the other languages I’ve implemented.

I started with the most basic code I could think of: 1 + 2

Then I made it more complex: x = 1 + 2 * 3

And then I even added statements: if x > y: z = 3

Great, I made a calculator… Not what I meant with this challenge! I was already over the limit too. That is when I zoomed out and made a list of elements that look Pythony, while also realizing that my code golf skills were not up to snuff to make it fit in 512 bytes.

Maybe I can do it in 1024 bytes? First, make it work, and then make it small.

The parser

The actual CPython implementation tokenizes the Python source, parses it into an abstract syntax tree, performs some analysis and optimizations, emits bytecode, and then interprets the bytecode.

This won’t really do any of that.

The state is held in a handful of global variables. It uses a fixed-length array (999 for now) that will hold the raw Python code. The variables and function names all fit into a single array.

char src[999];       /* Entire program without most spaces. */
int  vars[256];      /* Symbol table.                       */
int  pos;            /* Next character in src.              */
int  ch;             /* Current character in src.           */
int  line_start;     /* Where the current line starts.      */

The expressions are handled like any other recursive descent parser, and they are executed along the way. For example:

int parse_sum(void) 
    int value = parse_term();
    while (ch == '+' 

Straightforward so far.

There is no error handling of any kind! It makes a lot of assumptions based on the correctness of the code. For example, it assumes that the keywords are all typed out correctly.

    if (ch == 'w' || ch == 'i' || ch == 'f') {
        /* ---- while / if / for ---- */
        int keyword = ch;
        int loop_var = 0;

        if (keyword == 'f') c==10;)Y();p=u; else if (keyword == 'w')
            pos += 4;                     /* skip "hile"           */
        else 
            pos += 1;                     /* skip "f" of "if"      */

It also assumes the token boundaries are correct and strips out most whitespace. It keeps indentation and spaces in string literals.

It is limited to variable names of a single, lowercase character, which allows us to do symbol table lookups directly:

    if (ch > 96) c=c-9?c:32;h^=c==34;s[q]=c;q+=c-32?1:!m

Control flow magic

The function for executing blocks of code continues until the indentation decreases. When that happens, it returns, and it is up to the caller to handle the next line. So, it is using the C program’s call stack to handle the recursion.

void run_block(int min_indent) {
    for (;;) {
        int indent = read_indent();

        if (ch == '\n')                       
            continue;

        if (indent < min_indent || ch == 0) ⚡

But what about loops?!

Since nothing is compiled, loops work by jumping backwards and reparsing the source each iteration. Both while and for loops keep track of the position of the condition expression. After the body executes, it jumps back to that position and continues parsing.

Functions work in the same way. When parsing the definition, the symbol table remembers the position of the function in the source code. Then when parsing a function call, the caller location is saved, the parser jumps to the function body, executes the body, and restores the caller location when it reaches the end.

It is quite beautiful what we can do even with no intermediate representation! The interpreter maintains very little state too.

Minify!

I haven’t code golfed much. Trimming the variable names and whitespace is obvious, but how do I save the big bytes?

There exists an ancient, forgotten website called Stack Overflow where the code magicians of yesteryear shared their knowledge. I learned a lot of ideas from Tips for golfing in C.

Since rules only exist in your imagination, I did have to get creative. Some of those tips rely on “features” specific to GNU C89. This is not tomfoolery! This is conventional fiddle-faddle. Here is what I did to shave off bytes from the readable version:

  • Single-letter variable and function names
  • Assume the compiler will link libc
  • Use globals for temp variables
  • Globals are zero initialized
  • C89 allows variable declarations to be implicitly int and functions are assumed to return int
  • Use function parameters as temp variables that are preserved on the call stack
  • ASCII values instead of character literals
  • Ternary operator and comma operator
  • Bitwise operations instead of logical operations

For example, the parse_sum(void) function that I showed earlier was golfed down to e()for(g=f();c==42. It uses ASCII values to shave a few bytes.

After everything, the golfed version is 1024 bytes!

The final readable version is over 4800 bytes. I originally had several more features but I kept cutting to make it fit. The comparison expressions were next on the chopping block, since that eats up a lot of bytes and truthiness still works without them: if n%15:.

If all I cared about was making fizzbuzz work, I think I could get below 800 bytes! There are probably other golfing tricks too.

A screenshot of a terminal checking the byte length of the golfed code, compiling it, and running fizzbuzz with it.

Here is the golfed source in all its glory:

char s[999];v[256],p,c,x,y,z,w,u;G()for(;I()>iI()c=c-9?c:32;h^=c==34;s[q]=c;q+=c-32?1:!mY()🔥f()for(g=f();c==42t(g,h)c==10;)Y();p=u;e()c=c-9?c:32;h^=c==34;s[q]=c;q+=c-32?1:!mE(a,q)💬S(i)for(;I()>iQ()h;m=c>32B(i,q,j,k,a,m,n){for(;;){j=I();if(c==10)continue;if(j96)💬Y();}}}main(q,m,h){for(h=m=q=0;~(c=getchar());){c=c-9?c:32;h^=c==34;s[q]=c;q+=c-32?1:!m|h;m=c>32|m&&c-10;}B(0);}

In the end, I was able to implement these features:

  • Integer variables (single letter) and literals
  • Variable assignment
  • Arithmetic with + – * % with precedence (unary + – only works at the beginning of an expression)
  • Comparisons with < > <= >= == (only one per expression)
  • Integer truthiness
  • if and else
  • while loops, including else blocks
  • for x in range(y) loops, including else blocks
  • Function definitions with no arguments
  • Function calls, even recursive
  • Indent-based blocks (without scope)
  • print with a single string literal or integer expression
  • Comments

I don’t think I will be doing any code golf challenges again in the near future. The process was quite tedious, going back and forth between the gulfing-in-progress version and the original version to try to understand what I changed just 2 minutes ago. Both versions are on GitHub.

Now it is your turn. What does your Python in 1024 bytes look like?

{💬|⚡|🔥} **What’s your take?**
Share your thoughts in the comments below!

#️⃣ **#Making #Python #interpreter #bytes**

🕒 **Posted on**: 1788737170

🌟 **Want more?** Click here for more info! 🌟

By

Leave a Reply

Your email address will not be published. Required fields are marked *