How I Built a Python Compiler That Runs in Your Browser, With Real input()
Originally published on Medium. I'm Durim, and I build HTML Suite, a set of free developer tools that run entirely in the browser: an HTML editor, JSON and YAML formatters, converters and a Python online compiler. The
Originally published on Medium.
I'm Durim, and I build HTML Suite, a set of free developer tools that run entirely in the browser: an HTML editor, JSON and YAML formatters, converters and a Python online compiler.
The Python compiler runs real Python 3.13 on your own device using Pyodide (CPython compiled to WebAssembly). No server executes your code, nothing is uploaded, and you don't need an account. That part was surprisingly easy. The hard part was one tiny built-in function: input().
Why input() breaks in the browser
In normal Python, input() simply pauses the program until you type a line. In the browser, that is a problem. Pyodide runs inside a Web Worker, and everything in JavaScript is asynchronous. A worker can't just stop and wait for the page to send it some text.
That's why most online Python editors make you type all input into a separate "stdin" box before you press Run. If you forget, or the program asks for one more line than you provided, you get this:
EOFError: EOF when reading a line
My first version did exactly the same, and it was the most common bug people hit. Beginners write name = input("Your name? ") as one of their very first programs, so it had to work properly.
The fix: SharedArrayBuffer and Atomics.wait
The trick is to let the worker genuinely block. A SharedArrayBuffer is memory that both the page and the worker can see. Inside a worker, Atomics.wait() puts the thread to sleep until another thread changes a value in that shared memory.
So the flow looks like this:
- Python calls
input(). - The worker tells the page "I need a line" and goes to sleep with
Atomics.wait(). - The page shows an input field right below the output.
- When you press Enter, the page writes your text into the shared buffer and wakes the worker with
Atomics.notify(). - The worker reads the line and hands it back to Python, which continues as if nothing happened.
Simplified, the worker side is just this:
function readLine() {
Atomics.store(state, 0, 0);
postMessage({ type: "input-request" });
Atomics.wait(state, 0, 0); // sleep until the page answers
const len = Atomics.load(state, 1);
return decoder.decode(bytes.slice(0, len));
}
pyodide.setStdin({ stdin: readLine });
Because the Python interpreter never actually stops running, everything is preserved across the pause: variables, random numbers, open loops. You can ask for five inputs in a row and it behaves like a real terminal. UTF-8 works too, so "GrΓΌΓe" or emoji come through fine.
The catch: cross-origin isolation
Browsers only allow SharedArrayBuffer on pages that are "cross-origin isolated". That means sending two HTTP headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: credentialless
These headers are strict: every resource the page loads from another domain has to allow it. Pyodide is loaded from jsDelivr, which already sends the right headers, so that worked. I chose credentialless instead of require-corp so that third-party scripts without special headers still load (just without cookies).
I only set these headers on the Python page and its worker, not on the whole site, so nothing else on HTML Suite is affected.
One more surprise: the site uses Next.js, and when you click an internal link, Next.js swaps the page without a full reload. That means the new headers never apply, and the page isn't isolated. The fix was to make every link to the Python compiler do a full page load.
A fallback for browsers that can't do it
Not every browser supports credentialless. In that case the page doesn't crash with EOFError. It shows a clear message and opens the classic stdin box instead, so you can still provide input the old way.
Try it
You can try it here: htmlsuite.com/python-online-compiler. Paste this and press Run:
name = input("What's your name? ")
age = int(input("How old are you? "))
print(f"Hi {name}, in 10 years you'll be {age + 10}.")
The rest of HTML Suite works the same way: free, in your browser, no signup. There's an HTML editor with live preview, JSON, XML and YAML tools, Word to HTML, Base64, a JWT decoder, a regex tester and color tools.
If you try it and something doesn't work, or a tool you need is missing, I'd really like to hear about it in the comments.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.