# 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: ```ts check import { 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 over ``` ```python check from withruntime import Sandbox def 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 over ``` Variables 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](/docs/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: ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create({ network: { internet: false } }); await sbx.files.write("/workspace/main.c", '#include \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: ```ts check import { 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: ``` CPU: 20,000 × 40 s / 3,600 × $0.025 = $5.56 Memory: 20,000 × 10 min / 60 × 2 GiB × $0.0075 = $50.00 Total: $55.56 ``` A 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](/docs/pricing)). ## Start ```bash no-run npx withruntime sandbox run --trial -- node -e 'console.log(6 * 7)' ``` Approve the browser link once. `runtime sandbox run-code - --lang go < main.go` runs a file in any interpreter language from a terminal ([CLI](/docs/cli)). Related: [online IDE on sandboxes](/use-cases/online-ide), [coding interview platform](/use-cases/coding-interview-platform), [grade student code](/use-cases/grade-student-code), [run untrusted LLM code](/use-cases/run-untrusted-llm-code), [Go in a sandbox](/languages/go). Facts on this page were checked on 25 September 2026.