3 August 2025
Running Python in the Browser
The web has traditionally been JavaScript's domain, but what if you could run Python directly in the browser? With Pyodide, this becomes not just possible, but practical. In this post, I'll share my experience implementing a full-featured Python runtime for my personal website, complete with interactive code execution and execution control.
What is Pyodide?
Pyodide is a Python distribution for the browser and Node.js based on WebAssembly. It makes it possible to install and run Python packages in the browser, including scientific packages like NumPy, Pandas, and Matplotlib. Think of it as bringing the power of a Jupyter notebook directly to your web application.
The Architecture: WebWorkers and Isolation
One of the biggest challenges with running Python in the browser is preventing long-running computations from blocking the main UI thread. The straightforward solution is to use a WebWorker architecture that completely isolates Python execution:
The worker handles all Python execution while the main thread manages the UI. This means you can run complex computations without freezing the interface. The worker loads Pyodide along with essential packages:
The SharedArrayBuffer Challenge
How do you stop a long-running Python script that's executing in a WebWorker? Traditional approaches like worker.terminate()
would destroy the entire Python environment, forcing a reload. Here's where SharedArrayBuffer comes in: they allow us to, as the name suggests, share data between multiple JavaScript contexts.
This creates a single byte of shared memory that acts as our "stop button." When a user clicks the stop button during execution:
On the worker side, Pyodide can check this buffer and gracefully interrupt execution:
This approach is elegant because it allows for graceful interruption - Python can clean up resources, close files, and handle the interruption properly rather than being brutally terminated.
It's important to mention that SharedArrayBuffer requires Cross-Origin-Embedder-Policy
and Cross-Origin-Opener-Policy
policies:
- For Sveltekit you can create a hook.server.ts file
- For Vite, you can set the server.headers directly in the vite.config.ts file
- If you are deploying in Vercel, then the headers must be set in the vercel.json file
Making the Experience Interactive
Real-time Output Streaming
Traditional code execution shows results only after completion. For a true notebook-like experience, real-time output streaming captures print()
statements and displays them as they happen. The Pyodide worker intercepts stdout and stderr, sending output back to the main thread via postMessage
for immediate display, creating a live terminal-like experience.
Smart Expression Evaluation
To mimic Jupyter notebook behavior, the system automatically displays the result of the last expression in a code cell:
Handling tqdm
Progress Bars
One particularly tricky challenge was supporting progress bars from libraries like tqdm
. These typically use carriage returns (\r
) to overwrite the same line, which doesn't work well in HTML. The solution involved creating a custom WebTqdm
class that outputs each progress update on a new line while maintaining the visual progress indication. The worker detects progress bar output and handles it with special formatting for better web display.
Error Handling and Debugging
The Python runtime also provides intelligent error handling with line highlighting. When an error occurs, the problematic line is visually highlighted in the code editor, making debugging much easier:
Try it Yourself!
Nothing beats hands-on experience! The code blocks below demonstrate the full capabilities of this Python-in-browser implementation. Each block is fully interactive - you can modify the code, run it, and see the results in real-time.
Data Analysis with Pandas and NumPy
Data Visualization
Interactive Computation
Want to see more about how this works? Check out the source code for this website, where you can explore the full Pyodide integration in detail.