How to Run Python in the Browser Using the Pyodide API
Python JavaScript Pyodide
When building websites that teach coding or run data scripts, developers traditionally rely on backend servers. When a user clicks “Run,” the code travels to a server, executes inside a secure environment, and sends the results back to the frontend.
While this model works, it requires server maintenance, introduces network lag, and brings security risks from executing untrusted user code on your infrastructure.
An alternative approach is to use the Pyodide API to run Python code directly inside the user’s web browser. This creates a completely serverless environment where code runs instantly and securely at zero server cost.
How it Works
Pyodide is a Python distribution for the browser and Node.js, built on WebAssembly. It exposes a clean JavaScript API that allows you to load packages, share variables, and execute Python scripts directly inside your frontend application code.
Instead of deploying a fleet of servers, the browser itself becomes the runtime container:
graph TD
A[Code Input UI] --> B[Pyodide JavaScript API]
B --> C[In-Browser Python Runtime]
C --> D[Captured Text Logs]
D --> A
Step 1: Loading Pyodide Efficiently
The Pyodide core library is large. Downloading and initializing it on every interaction will cause your web application to lag. To keep things fast, the runtime should be loaded exactly once when your application loads, and then saved globally for reuse.
First, add the Pyodide script tag to your HTML:
<script src="https://cdn.jsdelivr.net/pyodide/v0.29.4/full/pyodide.js"></script>
Then set up an initialization function to manage the Pyodide instance:
let pyodideInstance = null;
async function getPyodide() {
// If already loaded, return the existing instance
if (pyodideInstance) return pyodideInstance;
// loadPyodide is available globally after the script tag loads
pyodideInstance = await loadPyodide({
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.29.4/full/",
});
return pyodideInstance;
}
Step 2: Catching Python Output (stdout)
If you execute standard Python code like print("Hello World") using Pyodide, the text goes to the hidden browser developer console by default. To display this text on your website’s interface, you must use the Pyodide API to intercept the standard output (stdout) stream and route it into a local string variable.
The setStdout API accepts a batched handler, which is called with a complete line of text each time Python flushes output:
async function runPythonCode(userCode) {
const pyodide = await getPyodide();
if (!pyodide)
return { success: false, output: "Python environment not loaded." };
let outputBuffer = "";
// Intercept print() statements and route them to our buffer variable
pyodide.setStdout({
batched: (msg) => {
outputBuffer += msg + "\n";
},
});
try {
// Run the Python script asynchronously using the API
await pyodide.runPythonAsync(userCode);
return { success: true, output: outputBuffer };
} catch (error) {
// Catch syntax mistakes or runtime errors from the Python interpreter
return { success: false, output: error.message };
}
}
Preventing UI Freezes with Web Workers
JavaScript inside web browsers runs on a single main thread. If a user runs an accidental infinite loop, such as:
while True:
pass
The browser thread executing that code will consume 100% of the CPU allocation. The website freezes, buttons stop responding, and the browser tab will eventually crash.
The Fix: Web Workers
To solve this, you can run the Pyodide API inside an isolated background Web Worker. Web Workers execute scripts completely separate from your main user interface thread. If the Python script gets stuck in a loop, the background worker locks up but the website itself stays active, allowing you to kill and restart the worker thread safely.
Conclusion
Using the Pyodide API shifts the work of running code entirely to the client side. It allows you to build fast, highly scalable coding platforms and software tools without the financial overhead or security risks of managing a dedicated backend server fleet.