Online Coding Judge (LeetCode / HackerRank)
Problem statement
Design a platform where users submit code in many languages against problems with hidden test cases, and get a verdict (Accepted / Wrong Answer / TLE / MLE / RE) within seconds — securely running untrusted code at scale.
Requirements
Functional
Problems with statements and hidden test cases; multiple languages.
Submit code; receive verdict, runtime and memory per test.
Run custom input (playground) without judging.
Contests: fixed window, leaderboard, penalty rules.
Non-functional
Verdict within 5–10 s for typical submissions; fairness (same hardware class).
Security: untrusted code must not access network, other users' data, or the host.
Scale: 1M submissions/day; contest bursts of 10k submissions/min.
Reproducible verdicts (same result on re-run).
Back-of-the-envelope estimates
- 1M/day ≈ 12/s average; contest peak ~170/s.
- Each run: compile 1–5 s + tests (20 × 1 s worst) → budget ~10 s CPU → 170/s × 10 s = 1,700 concurrent sandboxes at peak → ~200–400 worker nodes (8 sandboxes each) — autoscale before contests.
- Submission storage: 1M × 5 KB code + 2 KB result = 7 GB/day.
- Test data: 5k problems × 50 tests × 1 MB = 250 GB in object storage, cached on workers.
API
POST /v1/submissions { problemId, language, code, contestId? } → 202 { submissionId }
GET /v1/submissions/{id} → { status: queued|running|done, verdict, time, memory, perTest[] }
WS submission.{id} → status updates
POST /v1/run { language, code, stdin } → { stdout, stderr, time }
GET /v1/problems/{id} GET /v1/contests/{id}/leaderboard
Data model
problems (problem_id, title, statement, limits:{timeMs, memoryMb}, languages[], test_bundle_key)
test_cases (problem_id, idx, input_key, expected_key, is_sample, weight)
submissions (submission_id, user_id, problem_id, contest_id, language, code_key, status, verdict, time_ms, mem_kb, created_at)
test_results (submission_id, test_idx, verdict, time_ms, mem_kb)
contest_scores(contest_id, user_id, score, penalty, updated_at) + Redis ZSET for the live leaderboard
High-level design
- Submission service stores code (object storage), inserts
queued, publishes to a queue partitioned by priority (contest > practice > playground). - Judge workers pull a submission, fetch the language image and the problem's test bundle (locally cached), and run each test in an isolated sandbox: a container/gVisor/Firecracker micro-VM with no network, read-only filesystem, seccomp profile, cgroup CPU/memory limits, wall-clock and CPU-time limits, process count limits, and a non-root user.
- Verdict: compile errors short-circuit; tests run in order (stop at first failure for practice; all for contests if scoring is partial); compare output with a checker (exact, token-based, or a special judge program for multiple valid answers).
- Results written to the DB and pushed to the client via WebSocket/polling.
- Contest service updates scores on accepted verdicts and maintains a Redis leaderboard; final standings computed from the DB after the contest.
- Autoscaling of worker pools by queue depth; pre-warmed pools before contests.
Architecture diagram
Deep dives
Sandbox layers: language runtime image → container with seccomp/cgroups → gVisor or micro-VM for kernel isolation. Never run untrusted code in a plain container as root.
Fair timing: measure CPU time, not wall time, and pin sandboxes to dedicated cores; run on a homogeneous instance type.
Compile once, run many: compile in the sandbox, then fork per test with fresh limits.
Checkers: floating-point tolerance, unordered outputs, and multiple valid answers need a special judge; keep it as a separate program with its own limits.
Anti-abuse: rate limits per user; disk quota; fork-bomb protection via pid limits; kill on limit breach.
Bottlenecks & mitigations
Contest start burst → pre-scale worker pools; queue with priority; playground runs deprioritised.
Test bundle downloads → per-worker cache keyed by problem version; warm popular problems.
Sandbox startup time (~100 ms containers, ~150 ms Firecracker) → pool of pre-created sandboxes.
DB writes for per-test results → batch per submission.
Failure modes
- Worker crash mid-judging: message not acked → redelivered; judging is idempotent (results overwritten by submission id).
- Sandbox escape attempt: kernel isolation + no network + read-only FS; workers hold no secrets; rotate images.
- Flaky verdict (timing near the limit): run the failing test again once; report the better result; keep limits generous.
- Queue outage: submissions stay
queuedin DB; a replay job re-publishes.
Follow-up questions interviewers ask
How would you detect plagiarism across submissions?
How would you support interactive problems (judge talks to the program)?
How would you run a 100k-participant contest with < 30 s verdicts?