Design a Code Execution Sandbox / Online Judge
Pick the isolation boundary deliberately (microVM/Firecracker as the strong default, hardened seccomp container as the middle ground, never a bare container for hostile code), bound every resource with cgroups plus timeouts plus a pids limit plus no network, run each submission in a fresh throwaway sandbox behind a queue and autoscaling worker pool with a warm pool, and stream results while enforcing per-user fairness.
The isolation boundary is the core decision
A code execution sandbox (an online judge like LeetCode, a CI runner, or this platform's own code runner) runs untrusted user code safely at scale. The defining decision is the isolation boundary: how strong a wall you put between hostile code and your host and other users. Assume the code is actively hostile (fork bombs, network exfiltration, kernel-escape attempts), because at contest scale someone will try.
The isolation spectrum
From weakest and cheapest to strongest and heaviest: a plain OS process with rlimits is trivially escapable and unacceptable for hostile code. A container (Docker) is convenient and starts fast but shares the host kernel, so a kernel vulnerability is a full escape; a container alone is not a security boundary for hostile code. A hardened container (seccomp to whitelist syscalls, AppArmor or SELinux, non-root user, read-only filesystem, dropped capabilities) is a reasonable middle ground that shrinks the attack surface dramatically. gVisor puts a user-space kernel between the code and the host kernel, intercepting syscalls so a kernel bug is much harder to reach, at some performance cost. A microVM (Firecracker) or Kata Containers gives each submission its own tiny virtual machine with its own guest kernel and hardware-virtualization isolation, which is near-VM strength but boots in about 100ms, making it the strong default for untrusted code.
Plain OS process (rlimits): trivially escapable. Starts instantly and costs nothing, but the wall is paper: trivially escapable and unacceptable for hostile code.
Interview nuance: the senior move is to name the spectrum and commit: "I would use Firecracker microVMs for true kernel isolation with fast startup, falling back to a hardened seccomp container if microVMs are not available." Saying "run it in a Docker container" and stopping there fails the security bar, because a container shares the host kernel.
Resource limits and architecture
Use cgroups to cap CPU shares and memory (with a hard OOM kill), a wall-clock and CPU-time timeout to kill infinite loops, a pids limit to defeat fork bombs (a fork bomb without a pids cap exhausts the process table), disk quotas to stop a submission from filling the disk, and no network by default (or a strict egress allowlist) to prevent data exfiltration and abuse. Every submission runs in a fresh, throwaway sandbox that is destroyed after the run, so no state leaks between users.
A stateless API accepts submissions and immediately enqueues them onto a durable queue (SQS, Kafka), returning a job id. A pool of sandboxed workers pulls jobs, executes each in a fresh sandbox, and reports results. The queue decouples submission rate from execution capacity, so a contest spike buffers instead of overwhelming the fleet, and workers autoscale on queue depth. Because microVM cold start still costs latency, keep a warm pool of pre-booted sandboxes ready to accept a job, then destroy each after use.
Users want to see output as it runs, so stream stdout, stderr, and per-test progress back over SSE or WebSocket, store the final verdict durably, and cap output size so a submission that prints forever cannot exhaust memory or the client. Per-user rate limits and concurrency quotas so one user cannot monopolize the pool, and treat the sandbox host itself as potentially compromised by running the whole fleet in an isolated network segment with no access to production.
POST /submit -> API (stateless) -> durable queue (SQS/Kafka) -> job id
warm pool of microVMs -> worker pulls job -> fresh Firecracker VM
cgroups (cpu/mem), timeout, pids limit, no network, disk quota
stream stdout/stderr/test-progress (SSE) -> store verdict -> destroy VM
Recap: pick the isolation boundary deliberately (microVM/Firecracker as the strong default, hardened seccomp container as the middle ground, never a bare container for hostile code), bound every resource with cgroups plus timeouts plus a pids limit plus no network, run each submission in a fresh throwaway sandbox behind a queue and autoscaling worker pool with a warm pool for latency, and stream results while enforcing per-user fairness.
Apply
Your turn
The task this lesson builds to.
Design a code execution sandbox / online judge that runs untrusted user submissions safely at scale, and justify your isolation boundary, resource limits, queueing, and result streaming.
Think about
- What isolation boundary is strong enough to run hostile code, and what are the tradeoffs of each option?
- How do you bound CPU, memory, time, disk, and network so one submission cannot harm the host or others?
- How do you absorb bursty submissions and stream results back to the user?
Practice
Make it stick
A second problem on the same idea, so it survives past today.
Design the execution backend for a browser-based coding platform like Replit or CodeSandbox, where each user gets a long-lived interactive dev environment (not a one-shot judge), can install arbitrary packages and run a web server, thousands of environments run concurrently, and cost per idle environment must stay near zero.
Think about
- Why does a long-lived untrusted workload compound kernel-sharing risk?
- How does snapshot-and-resume make idle cost near zero?
- How do preview subdomains wake a paused environment on demand?