How to run a coding playground for your users
Run each visitor's code in its own microVM sandbox with the internet off, a time limit per run, and a warm interpreter between runs.
On Runtime 20,000 playground sessions a month cost about $55.56, a quarter of a cent each, on 1 vCPU, 2 GiB sandboxes at the rates in force on 25 September 2026. Runtime bills the CPU the code uses, so a session where the visitor spends most of the time reading and typing costs little, and one interpreter covers Python, JavaScript, TypeScript, R, Java, Bash and Go, with gcc and g++ for C and C++.
The short answer
One sandbox per browser session, found again by name, with the network cut and a limit on every run:
TypeScriptimport { Sandbox } from "withruntime";type Language = "python" | "javascript" | "typescript" | "go" | "java" | "r" | "bash";export async function runSnippet(sessionId: string, language: Language, code: string) { const sbx = await Sandbox.getOrCreate(`play-${sessionId}`, { vcpu: 1, memoryMiB: 2048, idlePauseSeconds: 300, // pause five minutes after the last run network: { internet: false }, maxCostMicros: 100_000, // refuse a create whose first lease could cost more than 10 cents labels: { kind: "playground" }, }); const cell = await sbx.interpreter.run(code, { language, timeoutMs: 10_000 }); return { status: cell.status, // "ok", "error" or "timeout" stdout: cell.stdout, stderr: cell.stderr, error: cell.error?.traceback ?? null, };}console.log(await runSnippet("s-1", "python", "total = sum(range(10))\nprint(total)"));console.log(await runSnippet("s-1", "python", "print(total * 2)")); // state carries overPythonfrom withruntime import Sandboxdef run_snippet(session_id: str, language: str, code: str) -> dict: sbx = Sandbox.get_or_create( f"play-{session_id}", vcpu=1, memory_mib=2048, idle_pause_seconds=300, # pause five minutes after the last run network={"internet": False}, max_cost_micros=100_000, # refuse a create whose first lease could cost more than 10 cents labels={"kind": "playground"}, ) cell = sbx.interpreter.run(code, language=language, timeout_ms=10_000) return {"status": cell["status"], "stdout": cell["stdout"], "stderr": cell["stderr"], "error": (cell["error"] or {}).get("traceback")}print(run_snippet("s-1", "python", "total = sum(range(10))\nprint(total)"))print(run_snippet("s-1", "python", "print(total * 2)")) # state carries overVariables persist between runs, the way a notebook works, so a visitor's second run can use what the first defined. To give a clean slate, stop the sandbox and the next run makes a new one.
Languages, and how each one runs
| Language | How the playground runs it | First use in a sandbox |
|---|---|---|
| Python 3.12 | Interpreter, state kept between runs | Ready; NumPy, pandas, matplotlib included |
| JavaScript, TypeScript | Interpreter, on Node.js 24 | Ready |
| Bash | Interpreter | Ready |
| Go | Interpreter; keeps functions, types and imports | Installed once, about 35 seconds |
| Java | Interpreter | Installed once, about 35 seconds |
| R | Interpreter; plots return as PNG | Installed once, about 90 seconds |
| C, C++ | gcc or g++ through exec, then the binary |
Ready |
| Rust | rustup baked into a custom image |
Build the image once |
The install times were measured on 23 September 2026. To skip them, build an image with those languages and start playground sandboxes from it (custom images).
C and C++ go through a file and a fixed command, so the visitor's code is data, never part of the shell line:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ network: { internet: false } });await sbx.files.write("/workspace/main.c", '#include <stdio.h>\nint main(){puts("hi");}\n');const build = await sbx.exec(["gcc", "-O2", "-o", "main", "main.c"], { timeoutMs: 20_000 });const run = build.exitCode === 0 ? await sbx.exec(["./main"], { timeoutMs: 5_000 }) : build;console.log(run.exitCode, run.stdout, run.stderr);Stop a playground from being abused
A public playground runs code from strangers. Each guard below is enforced outside the code's reach:
| Abuse | Guard |
|---|---|
| Escaping to your servers | A Firecracker microVM with its own kernel per session |
| Sending traffic out | internet: false, enforced on the host; root in the sandbox cannot undo it |
| An infinite loop | timeoutMs per run; the run returns as a timeout with the output so far |
| Flooding the page with output | Each result holds at most 64 KiB of stdout and 64 KiB of stderr |
| Filling memory or disk | The sandbox's own memoryMiB and diskMiB; other sessions are unaffected |
| Running up your bill | maxCostMicros per create, a daily spending limit on the key, pause when idle |
| Too many sessions at once | Creates past the account limit wait for room instead of failing |
Runtime's acceptable use policy applies to what your
visitors run too, so keep the internet off unless a lesson needs it; when one
does, allow only that host with network.allow.
Stream output as it prints
For programs that print over time, stream to the browser instead of waiting:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ network: { internet: false } });await sbx.interpreter.run( "import time\nfor i in range(3):\n print(i, flush=True)\n time.sleep(1)", { timeoutMs: 10_000, onStdout: (text) => process.stdout.write(text), // send each chunk over your WebSocket },);What a coding playground needs
| Need | How Runtime covers it |
|---|---|
| Many languages | Seven interpreter languages, plus gcc and g++ |
| A session that remembers | Named sandboxes with getOrCreate; variables persist between runs |
| Fast first run | 351 ms median from create to first Python result, 24 September 2026 |
| Cheap idle sessions | Measured CPU while running; idlePauseSeconds pauses quiet ones |
| Isolation between visitors | One microVM per session |
| Nothing leaves | Internet off per sandbox |
What it costs
Take 20,000 sessions a month. Each keeps a 1 vCPU, 2 GiB sandbox running 10 minutes, idle pause included, with the visitor's runs using 40 CPU-seconds, and is stopped when the session ends:
TextCPU: 20,000 × 40 s / 3,600 × $0.025 = $5.56Memory: 20,000 × 10 min / 60 × 2 GiB × $0.0075 = $50.00Total: $55.56A session's CPU never bills below the floor of a twentieth of a vCPU, which over 10 minutes is 30 CPU-seconds, so 40 is above it. Runtime charges $0.025 per vCPU-hour of measured CPU and $0.0075 per GiB-hour of memory (pricing).
Start
Terminalnpx withruntime sandbox run --trial -- node -e 'console.log(6 * 7)'Approve the browser link once. runtime sandbox run-code <id> - --lang go < main.go
runs a file in any interpreter language from a terminal (CLI).
Related: online IDE on sandboxes, coding interview platform, grade student code, run untrusted LLM code, Go in a sandbox.
Facts on this page were checked on 25 September 2026.