How to build a coding interview platform with sandboxes
Prepare a snapshot for each question, start a sandbox from it for each interview, run the candidate's code there, and record every edit.
On Runtime 400 interviews of 75 minutes cost about $17.85 a month, under 5 cents each, including stored snapshots for 20 questions, at the rates in force on 25 September 2026. A sandbox started from a snapshot comes up with the question's repository, packages and even running services already in place, so the candidate starts coding at once, and each candidate gets a Firecracker microVM of their own.
The short answer
Once per question, set up a sandbox and keep it as a snapshot. For each interview, start a copy, open a terminal for both people, and keep the lease alive while the interview runs:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();// Once per question: the starter repository with its dependencies installed.const setup = await runtime.sandboxes.create({ diskMiB: 8192 });await setup.files.upload("./questions/rate-limiter", "/workspace/task");await setup.exec("cd task && npm ci", { check: true, timeoutMs: 600_000 });const question = await setup.snapshot({ name: "q-rate-limiter", retentionDays: 365 });await setup.stop();// Each interview: a fresh copy of that machine.const interview = await runtime.sandboxes.create({ snapshot: question.id, name: "interview-2291", labels: { kind: "interview", question: "rate-limiter", candidate: "c-5120" },});const release = interview.keepAlive(); // interviews run past the one-hour leaseawait interview.network.set({ internet: true, allow: ["registry.npmjs.org"] });const term = await interview.terminal({ cols: 120, rows: 32, onData: (bytes) => process.stdout.write(bytes), // fan out to both browsers});term.write("cd task && npm test\n");// ... at the end of the interview: release(), then collect results (below).release();Pythonfrom withruntime import Runtimeruntime = Runtime()setup = runtime.sandboxes.create(disk_mib=8192)setup.files.upload("./questions/rate-limiter", "/workspace/task")setup.exec("cd task && npm ci", check=True, timeout_ms=600_000)question = setup.snapshot(name="q-rate-limiter", retention_days=365)setup.stop()interview = runtime.sandboxes.create( snapshot=question["id"], name="interview-2291", labels={"kind": "interview", "question": "rate-limiter", "candidate": "c-5120"},)release = interview.keep_alive() # interviews run past the one-hour leaseinterview.network.set(internet=True, allow=["registry.npmjs.org"])term = interview.terminal(cols=120, rows=32)term.write("cd task && npm test\n")release()A snapshot keeps running processes, so a question that needs a database or an API mock running can have it up before the candidate arrives. A snapshot is kept for the 1 to 365 days you set, and is copied off its server as soon as it is taken.
Record the session as a timeline
Interviewers want to see how a candidate got to the answer, not only the final file. A file watch reports every create, write, rename and remove as it happens; store each event with a timestamp and the file's new contents:
TypeScriptimport { Sandbox } from "withruntime";const sbx = await Sandbox.connect(process.env.INTERVIEW_SANDBOX_ID!);const timeline: { at: string; type: string; path: string; text?: string }[] = [];const watch = await sbx.files.watch( "/workspace/task", async (event) => { const text = event.type === "write" ? await sbx.files.readText(event.path) : undefined; timeline.push({ at: new Date().toISOString(), type: event.type, path: event.path, text }); }, { recursive: true, exclude: ["node_modules", ".git/**"] },);// ... when the interview ends:await watch.stop();console.log(timeline.length, "events recorded");Repeated writes to one file inside a batch arrive as one event with a count, so a save-on-every-keystroke editor does not flood the log. For a front-end question, the sandbox's desktop can also record the screen to MP4, stored on the sandbox's own disk (a desktop).
Grade with tests the candidate never saw
Keep the hidden tests out of the question's snapshot. When the interview ends, upload them and run them in the candidate's sandbox, with the internet off so the result depends on the code alone:
TypeScriptimport { Sandbox } from "withruntime";const sbx = await Sandbox.connect(process.env.INTERVIEW_SANDBOX_ID!);await sbx.network.off();await sbx.files.upload("./hidden-tests/rate-limiter", "/workspace/task/hidden");const result = await sbx.exec("npx vitest run hidden --reporter=json", { cwd: "/workspace/task", timeoutMs: 300_000,});await sbx.files.download("/workspace/task", "./archive/interview-2291");console.log(result.exitCode);await sbx.stop();The download keeps a copy of the candidate's final work for the hiring committee, and stopping the sandbox ends its billing.
Settings for a fair interview
| Question for your platform | Setting |
|---|---|
| May the candidate install packages? | network.allow with just the package registry, or network.off() |
| May they search the web or call an AI API? | Leave those hosts out of allow; the host enforces it, even against sudo |
| Does every candidate get the same machine? | Every interview starts from the same snapshot, size and image |
| What if the candidate's code hangs? | timeoutMs on each run, which returns as a timeout with the output so far |
| Which languages? | Any the image has; the interpreter adds Python, JavaScript, TypeScript, R, Java, Bash and Go |
| Who on the team can start interviews? | Owners, admins and developers; billing members cannot (teams) |
| Who did what? | The audit log records keys, members and network rule changes, kept 400 days |
What an interview platform needs
| Need | How Runtime covers it |
|---|---|
| A ready environment per question | Snapshots with processes, 1 to 365 days of retention |
| A shared terminal | terminal() over WebSocket; your back end sends its bytes to both people |
| A record of the session | files.watch events; desktop recording to MP4 |
| Hidden tests | Uploaded after the interview, run with the internet off |
| Isolation between candidates | A Firecracker microVM with its own kernel per interview |
| Sessions over an hour | keepAlive() from your server, or extend |
What it costs
Take 400 interviews a month. Each keeps a 2 vCPU, 4 GiB sandbox running 75 minutes, with the candidate's builds and tests using 0.1 of a vCPU on average, and the account keeps 20 question snapshots of 1 GB each:
TextCPU: 400 × 1.25 h × 0.1 vCPU × $0.025 = $1.25Memory: 400 × 1.25 h × 4 GiB × $0.0075 = $15.00Snapshots: 20 × 1 GB × $0.08 = $1.60Total: $17.85Runtime charges $0.025 per vCPU-hour of measured CPU, $0.0075 per GiB-hour of memory, and $0.08 per GB per 30-day month for a snapshot's own bytes; a block two of your snapshots share counts once (pricing).
Start
Terminalnpx withruntime sandbox run --trial --keep -- node --versionApprove the browser link that appears, then runtime sandbox snapshot <id>
keeps that machine as a question's starting point (CLI).
Related: coding playground, grade student code, online IDE on sandboxes, sandbox snapshots.
Facts on this page were checked on 25 September 2026.