Online Code Judge Interview Questions | JiQuest

add

#

Online Code Judge

System design deep dive · HLD

Design an Online Code Judge (LeetCode / Codeforces style): full high-level design.

Requirements, back-of-envelope capacity estimation, a high-level design diagram, a deeper deployment architecture diagram, sequence diagrams for submission execution and contest-time verdict aggregation, and an entity-relationship diagram for the data model - with the reasoning an interviewer expects behind every box and arrow.

2M/dayCode submissions
10K/sPeak during live contests
<5sVerdict latency, p99
EditorSubmit solution Submission svcenqueues job Sandbox workergVisor / cgroups VerdictAC / WA / TLE / MLE Hidden test casesper-problem streamed back to editor

1. Clarify requirements before drawing any box

A strong system design answer starts by pinning down scope out loud. For an online code judge, that means separating "run untrusted code" from "run untrusted code safely, fairly, and reproducibly at contest scale" - sandboxing and resource limiting are the actual crux of the problem, not compiling code.

Functional requirements

Submit codeAccept source code in a supported language for a given problem, from a single practice submission or a live contest.
Sandboxed executionCompile (if needed) and run the submission against a set of hidden test cases, isolated from the host and from other submissions.
Resource-limited judgingEnforce per-test CPU time, wall-clock time, and memory limits; classify the outcome per test.
Verdict aggregationCombine per-test results into a single verdict (Accepted, Wrong Answer, Time Limit Exceeded, Memory Limit Exceeded, Runtime Error) plus runtime/memory stats.

Non-functional requirements

Strong isolationA malicious submission must not be able to read other users' code, access the network, or affect the host or other sandboxes.
Deterministic, reproducible judgingThe same submission against the same test data must always produce the same verdict - contest fairness depends on it.
Burst scalabilityA live contest's "everyone submits in the last 2 minutes" pattern must not blow up queueing latency.
Fast feedbackPractice submissions should return a verdict within a few seconds so the edit-submit-debug loop stays fast.
Explicitly out of scope A full in-browser IDE with IntelliSense, plagiarism/similarity detection between submissions, and multi-language interactive/special-judge problems (where the judge itself runs custom validation code) are called out as extensions rather than core requirements, so the core design stays focused on the compile-sandbox-judge-aggregate loop.

2. Back-of-the-envelope capacity estimation

These numbers decide how many sandbox worker containers must be warm and ready, how test-case data should be distributed to workers, and whether a single queue is enough or partitioning by contest is required.

MetricAssumptionResulting estimate
Daily submissions2 million/day across practice + contests~23 submissions/sec average
Contest burst50,000 contestants, 40% submit in the last 2 minutes of a round~20,000 submissions in 120s ≈ 170/sec sustained, spiking past 400/sec
Execution cost per submission~15 hidden test cases × ~200ms each (compile + run)~3s of sandbox time per submission × 400/sec peak ≈ 1,200 concurrent sandbox slots needed
Storage per submission record~2 KB (source code, per-test results, verdict, timing)2M/day × 2KB × 3yr retention ≈ 4.4 TB
Test-case data per problem~5 MB average (input/output files across difficulty tiers)50,000 problems × 5MB ≈ 250 GB - fits object storage with a hot-tier cache
Why this matters The contest burst ratio (400/sec peak vs 23/sec average, a ~17x spike) is the number that justifies almost everything below: pre-warmed sandbox container pools rather than cold-starting containers on demand, and per-contest queue isolation so one contest's last-minute rush cannot starve unrelated practice submissions.

3. High-level design (HLD)

The HLD names the major components and the one-directional data flow between them, without committing yet to specific infrastructure, regions, or replica counts - that level of detail belongs in the architecture diagram in the next section.

Clientcode editor API gatewayauth, rate limit Submission servicewrite path Judge dispatcherassigns sandbox Problem/test store Sandbox poolgVisor containers Submissions DBverdicts, durable Per-contest queuesisolated priority Leaderboard cacheasync writer
Stateless servicesExecution/fast-path infraDurable storageAsync / contest path

What each box owns

Submission service (write path)

Accepts the source code and language, does a cheap static check (size limit, disallowed syscalls if statically detectable, basic syntax sanity), writes a submission row with status PENDING, and enqueues a judge job. It never executes code itself - keeping the public-facing API surface free of anything that touches the sandbox pool directly.

Judge dispatcher and sandbox pool

The dispatcher pulls jobs from the appropriate queue (contest-isolated or general practice pool), assigns each to a pre-warmed sandbox container, and streams the compiled binary plus each hidden test case's input in turn, enforcing CPU time, wall-clock time, and memory limits per test via cgroups and a gVisor (or Firecracker) isolation boundary. Sandboxes are single-use and destroyed after judging one submission, so no state or side effect can leak between submissions even if the code being judged is actively malicious.

Problem/test store

Holds problem statements, per-test input/output files, and per-problem resource limits, kept strictly separate from the submissions database - a hidden test case leaking into submission-facing storage would compromise every contest using that problem. Test data is content-addressed and cached at the sandbox-host layer so repeated judging of the same problem doesn't re-fetch large test files from object storage each time.

Submissions DB, per-contest queues, and leaderboard cache

The submissions database is the durable source of truth for verdicts and is written once judging completes. Per-contest queues exist specifically so that one contest's last-two-minutes submission storm cannot delay another contest's or practice mode's turnaround time. The leaderboard cache is updated asynchronously from verdicts so a burst of scoring recalculation never blocks the judging pipeline itself.

4. Detailed architecture diagram

The architecture diagram takes every HLD box and answers "how is this actually deployed?" - replica counts, sandbox host pools, regions, and the specific technology choice, which is what an interviewer is checking for once they've accepted the high-level shape.

Edge layer CDN / static editor assets API gateway + L7 LB Per-user submit rate limiter Static code lintersize/pattern pre-check Control plane Submission svc ×8 pods Judge dispatcher ×6 pods Contest queue (isolated) Practice queue (shared) Sandbox execution fleet (autoscaled) gVisor host pool ×200 nodes Warm container pre-pool cgroups limits per testCPU/wall-clock/memoryno network namespace Submissions DB Shard 0-3 Shard 4-7 sharded by user_id, verdict written once Problem/test store Object storage Host-local test cache content-addressed, immutable per problem Leaderboard pipeline Redis sorted sets (per contest) rebuilt from verdict stream
DecisionChoiceReasoning
Sandbox isolation technologygVisor (user-space kernel) or Firecracker microVMs, not bare DockerA plain container shares the host kernel; untrusted code with a kernel exploit could escape. gVisor/Firecracker add a real isolation boundary between the sandbox and the host kernel.
Queue partitioningPer-contest isolated queues, not one global FIFOA 50,000-contestant last-minute rush must not delay verdicts for an unrelated practice-mode user or a different concurrent contest.
Container lifecyclePre-warmed pool, single-use, destroyed after one submissionCold-starting a container per submission adds hundreds of milliseconds of unacceptable latency; reusing a container across submissions risks state leakage between untrusted code runs.
Test data distributionContent-addressed, host-local cache in front of object storageThe same problem's test cases are fetched repeatedly across thousands of submissions; caching by content hash avoids re-fetching immutable data on every judge run.

5. Sequence diagrams for the two critical flows

A sequence diagram is where an interviewer checks whether you actually understand call order, what is synchronous versus fire-and-forget, and exactly how a resource-limit violation short-circuits the rest of the test run.

5.1 Code submission and sandboxed execution

Client Submission svc Dispatcher Sandbox DB 1. POST /submit {code, lang, problem_id} 2. INSERT submission, status=PENDING 3. 202 Accepted {submission_id} 4. enqueue judge job 5. assign warm sandbox, load tests 6. compile; run test 1..N under cgroup limits 7. test 7: TLE (exceeded 2s CPU) 8. UPDATE verdict=TLE, per-test results, destroy sandbox

Step 3 returns immediately with a submission ID rather than blocking on execution, since judging can take several seconds; the client polls or subscribes for the verdict separately. Step 7 shows the judge stopping at the first failing test in this example (a common mode for practice problems to save compute), though many judges instead run all N tests to completion and report full per-test detail - a real design trade-off worth naming out loud between "fail fast" and "full diagnostic output."

5.2 Contest-time verdict aggregation under load

Sandbox Dispatcher DB Verdict stream Leaderboard 1. verdict=ACCEPTED, runtime=88ms 2. UPDATE submission verdict 3. publish verdict event (async) 4. ZADD score, apply penalty-time rule 5. 19,999 more verdicts arrive in next 90s (burst) 6. dispatcher autoscales sandbox pool 7. leaderboard batches ZADD writes, coalesces 8. standings refresh, ~2s staleness under burst

Step 3's async publish is what keeps the judging pipeline itself from ever being slowed down by leaderboard-update volume - the sandbox pool's job is only to produce a verdict, never to wait on standings math. Step 8's explicit "~2s staleness under burst" is a deliberate trade-off: contestants briefly see a slightly stale leaderboard during the heaviest submission spike rather than the judging pipeline itself backing up to keep the leaderboard perfectly real-time.

6. Entity-relationship (ER) diagram and schema

The data model has to answer three questions: where do hidden test cases live so they can never leak to a client, how is a submission's per-test detail stored without one giant JSON blob, and how does the schema support both "practice" and "contest, ranked by time" scoring modes.

submissions PK id BIGINT user_id BIGINT FK problem_id BIGINT language VARCHAR verdict ENUM runtime_ms INT submitted_at TIMESTAMP problems PK id BIGINT title VARCHAR statement_ref TEXT time_limit_ms INT mem_limit_mb INT difficulty ENUM is_public BOOLEAN created_at TIMESTAMP test_cases PK id BIGINT FK problem_id BIGINT input_ref TEXT expected_ref TEXT N1 1N one problem has many test_cases; one problem receives many submissions

Key modeling decisions

test_cases lives in a store the public API never queriesPhysically separate access path from submissions/problems metadata, so an application-layer bug in the public API cannot accidentally expose hidden inputs/outputs.
Per-test results are a side table, not a JSON blob on submissionsA separate submission_test_results(submission_id, test_id, verdict, runtime_ms) table keeps the hot submissions row narrow and lets per-test detail be queried/paginated independently.
time_limit_ms and mem_limit_mb live on problems, not globallyDifferent problems legitimately need different limits (a string-processing problem vs a graph-algorithm problem), so limits are data, not a hardcoded constant.
NoSQL alternativeA document store works well for problems (statement + limits as one document) but submissions' need for range queries (by user, by problem, by time) and joins for leaderboard math favors a relational or wide-column model.
Storage choiceUse whenWatch out for
Relational (Postgres), sharded by user_idYou need range queries ("all my submissions"), joins for leaderboards, and transactional verdict writes.Cross-shard contest-wide leaderboard queries need a separate read path (the Redis sorted-set cache) rather than hitting the shards directly.
Document/wide-column for problems + test_casesProblem statements and test data are read far more often than written and rarely need relational joins.Strict separation from submissions storage must still be enforced at the access-control layer, not just by data-modeling convention.

7. Deep dives interviewers actually probe

How do you sandbox untrusted code safely - containers vs microVMs?

A plain Docker container shares the host's Linux kernel, so a kernel-level exploit in the submitted code could escape to the host or to other tenants' containers. gVisor intercepts syscalls in a user-space kernel shim, and Firecracker runs each sandbox in a lightweight microVM with its own kernel - both add a real isolation boundary at the cost of a few milliseconds of overhead per syscall, which is an acceptable trade for the security guarantee at judge scale. Network access is disabled entirely inside the sandbox (no network namespace), since a submission has no legitimate reason to make outbound network calls.

// Simplified per-test execution with resource limits (Linux cgroups v2)
cgroup.set("cpu.max", "2000000 1000000");   // 2 CPU-seconds per 1s period
cgroup.set("memory.max", "268435456");      // 256 MB
Process p = sandbox.exec(compiledBinary, testInput, timeoutMs=2000);

How do you keep judging deterministic and reproducible?

Fixed compiler/interpreter versions pinned per language (never "latest"), a fixed CPU frequency/governor setting on judge hosts (to avoid turbo-boost variance affecting timing-sensitive verdicts), and test execution order that never depends on wall-clock scheduling artifacts. Floating-point comparisons in expected output use an epsilon tolerance rather than exact string match, since bit-for-bit float reproducibility across hardware is not guaranteed even for correct solutions.

Fail-fast vs run-all-tests - which should the judge do?

Fail-fast (stop at the first failing test) saves compute and is common for high-volume practice platforms; run-all-tests gives the submitter full diagnostic detail (useful in a learning context, or for contest problems with partial-credit scoring per subtask) at higher compute cost. Many production judges use a hybrid: fail-fast for the compile/first-test smoke check, then run all remaining tests only if that passes, balancing feedback quality against sandbox-time cost.

How do you prevent one contestant's runaway process from affecting others?

Every resource limit (CPU time, wall-clock time, memory, process/thread count, output size) is enforced at the cgroup/microVM level, not just measured after the fact - a submission that tries to fork-bomb or allocate unbounded memory is killed by the kernel-level limit itself, not by application code politely checking a counter. Because each sandbox is single-use and torn down after judging, a submission that somehow evades all limits and crashes its sandbox still cannot affect the next submission, which runs in a freshly created sandbox.

What is the single biggest bottleneck during a live contest?

Not the database writes - those are cheap, small rows. The real constraint is sandbox execution throughput during the last-two-minutes submission spike, since compute-bound judging cannot be cached or batched away like a read path can. The mitigation is pre-warming a sandbox pool sized for the expected peak (based on registered contestant count, known in advance) before the contest starts, plus autoscaling headroom, rather than reactively scaling once the queue is already backing up.

8. Summary: what a strong answer covers

Clarified scope before designingJustified every number with a calculationNamed a real sandbox isolation technology Per-contest queue isolationDeterministic, reproducible judgingAsync leaderboard off the judging critical path
Interview tip When asked to design an online code judge, the strongest signal is treating sandbox isolation as the actual hard problem - naming gVisor/Firecracker/cgroups specifically and explaining why plain Docker isn't enough - rather than glossing over execution as "run the code in a container" and spending all the design time on the database schema.
No comments
Leave a Comment