Tier 5 — Expert

Code Sandbox

Model how a sandbox runs untrusted code and captures its result — success, output, error, or timeout. The idea behind CI runners and online code judges.

🐍 Python 🟨 JavaScript

1. The Problem

Model a sandbox that runs a piece of code and reports a structured result: whether it succeeded, its output, any error, and whether it exceeded a time budget. The runner must never crash the host — a failing or slow job becomes a recorded result, not a thrown exception.

Why this project? Online judges (LeetCode), CI runners, and serverless platforms all run untrusted code and must isolate it. The result contract — success/output/error/timeout — and never letting a job take down the host is the core discipline, separate from the OS-level isolation.

2. How to Think About This

A sandbox needs a uniform result shape: { success, output, error }. Run the job inside a try/catch so an exception becomes success: false with the error captured, never a crash. Model the time budget by checking a reported duration against a limit and returning a timeout result. Real isolation uses OS primitives (separate processes, containers, namespaces) — here we model the contract and the never-crash guarantee, which is the part your code depends on.

3. The Build

sandbox.mjs
// Run `job` (a function) and always return a structured result —
// never throw, no matter what the job does.
export function runSandboxed(job, { timeLimit = 5, reportedTime = 0 } = {}) {
  // Model a time budget: a job reporting more than the limit is "killed".
  if (reportedTime > timeLimit) {
    return { success: false, output: "", error: "Timed out" };
  }
  try {
    const output = job();
    return { success: true, output: String(output), error: "" };
  } catch (e) {
    // A crashing job becomes a recorded failure, not a host crash.
    return { success: false, output: "", error: e.message };
  }
}

// Demo
console.log(runSandboxed(() => 2 + 2));                       // success
console.log(runSandboxed(() => { throw new Error("boom"); })); // caught error
console.log(runSandboxed(() => 1, { timeLimit: 1, reportedTime: 5 })); // timeout

4. Test & Prove

sandbox.test.mjs
import { runSandboxed } from "./sandbox.mjs";

let pass = 0, fail = 0;
function test(desc, fn) {
  try { fn(); console.log("  \u2713", desc); pass++; }
  catch (e) { console.log("  \u2717", desc, "\u2014", e.message); fail++; }
}
function assert(cond, msg) { if (!cond) throw new Error(msg || "assertion failed"); }

test("a successful job reports success and output", () => {
  const r = runSandboxed(() => 2 + 2);
  assert(r.success === true && r.output === "4");
});
test("a throwing job is caught, not propagated", () => {
  const r = runSandboxed(() => { throw new Error("boom"); });
  assert(r.success === false && r.error === "boom");
});
test("exceeding the time budget yields a timeout", () => {
  const r = runSandboxed(() => 1, { timeLimit: 1, reportedTime: 5 });
  assert(r.success === false && r.error === "Timed out");
});
test("the host never crashes — always a structured result", () => {
  for (const job of [() => 1, () => { throw new Error("x"); }, () => null]) {
    const r = runSandboxed(job);
    assert("success" in r && "output" in r && "error" in r);
  }
});

console.log(`\n${pass} passed, ${fail} failed`);

5. The Interface

node sandbox.mjs

{ success: true, output: '4', error: '' }
{ success: false, output: '', error: 'boom' }
{ success: false, output: '', error: 'Timed out' }

6. Run It

node sandbox.mjs
node sandbox.test.mjs

# Real isolation runs the code in a separate process/container, e.g. Node's
# child_process or worker_threads with a kill-on-timeout; the result contract
# above (success/output/error) stays exactly the same.

🎯 Try this next

  1. Run the job in a real Node worker_thread and kill it on a true timeout
  2. Add a memory limit alongside the time limit
  3. Capture console.log output from inside the job, not just its return value
  4. Return the duration so callers can see how close to the limit a job ran

What you practised

The sandbox result contract (success/output/error/timeout), the never-crash-the-host discipline with try/catch, and separating the result model (portable) from OS-level isolation (processes/containers).

Reference: Error Handling · Functions · Objects

Try It Yourself