Review: LeetCode
Design an online code judge that executes untrusted submissions.
Clarify this prompt before choosing components
Ask the highest-impact questions first, agree on priorities, and record the interviewer’s answers. If a detail is unspecified, state an assumption and explain which decision depends on it. The numbers in this worked lesson are exercise assumptions, not company facts.
| Ask the interviewer | Why the answer changes the design |
|---|---|
| Code execution, submissions or timed contests first? | Contest deadlines/leaderboards add fairness and ordering rules. |
| Languages, time/memory limits and untrusted code? | Use isolation, resource budgets and bounded output. |
| Can a submission be rejudged after tests change? | Pin judge versions and define result revision semantics. |
Use the Interview Playbook for the shared 45-minute sequence and fresher, intermediate, senior and staff evidence. Start with one complete operation. Extend the answer with the failure/recovery traces below, then defend a workload-specific alternative and a measurable rollout. Scope and evidence matter more than a large component list.
LeetCode: Understand the building blocks
Choose a mechanism because it satisfies a requirement. These explanations describe the exercise design, not undisclosed implementation choices at the named company.
LeetCode: Quick vocabulary
| Term | Meaning and role in this exercise |
|---|---|
| API: Application Programming Interface | The contract for requests, responses and errors between components. An API route does not itself enforce every business invariant. |
| CPU: Central Processing Unit | General-purpose compute whose time/concurrency must be bounded for judging, processing or encoding. |
| PK: Primary Key | The declared row identity in a database schema; it does not automatically enforce every cross-row business rule. |
| OOM: Out of Memory | A process cannot satisfy an allocation or is terminated by its resource policy. Bound inputs and actually reclaim failed worker resources. |
LeetCode: Sandbox and resource isolation
What it means: A sandbox restricts untrusted program execution through an explicit isolation boundary, privileges and resource/network policies.
Why this problem needs it: Submitted code is untrusted and may loop or allocate indefinitely.
How to use it: Run a pinned submission/test version in an isolated process/container/VM boundary, constrain CPU, memory, output and wall time, and actually terminate runaway execution.
Example in this design: Kill a runaway execution at the actual resource boundary; do not only mark its job timed out.
When it fits: Required when evaluating user code instead of trusting it as ordinary application code.
Alternative and limits: Do not execute untrusted code inside the web process. A container name or timeout flag alone does not prove isolation or resource reclamation.
LeetCode: Durable work queue and backpressure
What it means: A queue separates accepted work from execution. Backpressure limits admission or consumption when downstream capacity is finite.
Why this problem needs it: Judging should absorb a finite submission burst without blocking request threads.
How to use it: Persist a stable job identity, consume with bounded concurrency, acknowledge after the required durable transition, and cap retries/backlog according to policy.
Example in this design: A submission returns a durable job identity while bounded workers run the pinned suite.
When it fits: Useful for asynchronous work that must survive worker failure or absorb a finite burst.
Alternative and limits: A synchronous call is simpler for short bounded work. A queue does not create capacity, strict global ordering or exactly-once external effects.
LeetCode: Lease, ownership epoch and fencing
What it means: A lease grants temporary work ownership. A fencing token or epoch identifies a newer owner so stale owners can be rejected at the mutation boundary.
Why this problem needs it: A stale judge must not overwrite a newer verdict attempt.
How to use it: Claim a job under an atomic authority, increment its attempt/epoch, and require that value on result publication. The protected resource must enforce the check.
Example in this design: After attempt 10 owns the job, attempt 9 cannot publish a verdict.
When it fits: Useful when workers can pause, restart or overlap after a timeout.
Alternative and limits: A non-expiring lock risks blocking forever. Lease expiry alone cannot stop an old process that resumes; external effects need their own supported idempotency/reconciliation.
LeetCode: Projection and materialized view
What it means: A projection is a derived read model built from authoritative facts; it improves a particular read path and can be rebuilt.
Why this problem needs it: Leaderboard reads should not rerun all judging history.
How to use it: Consume versioned facts with replay-safe updates, expose a watermark/revision, and compare or rebuild from the authoritative history.
Example in this design: A verdict revision updates a replay-safe score view; duplicate delivery cannot add points again.
When it fits: Useful when read shapes or volume justify duplicating computation/state.
Alternative and limits: Read authoritative rows directly for a simpler strongly current baseline. A stale projection must not grant revoked access or decide a strict inventory transition.
Interview scope and guarantees
Browse problems, submit source code, execute hidden tests, return verdicts, and rank contest participants. Untrusted code must not access host resources, credentials, other submissions, or the network. Define CPU, memory, output, and wall-time limits separately.
Capacity worksheet
Assume 100,000 contestants submit once every 2 minutes: about 833 submissions/s. At 2 CPU-seconds/submission the workload consumes about 1,667 fully utilized CPU cores; headroom, startup and heavy languages increase the requirement. Polling 100,000 users every 5 seconds creates 20,000 status reads/s, motivating cached verdicts and backoff.
Concrete API contract
POST /submissions {problemId,language,source,contestId?} -> 202 {submissionId}
GET /submissions/{id} -> {state,verdict,runtime}
GET /contests/{id}/leaderboard?cursor=...
POST /submissions/{id}/cancelData model and access paths
submissions(id PK,user_id,request_key,request_hash,problem_version,source_key,state,generation); UNIQUE(user_id,request_key)
results(submission_id UNIQUE,judge_version,verdict,cpu_ms,memory_bytes)
first_solves(contest_id,user_id,problem_id,accepted_at); PK(contest_id,user_id,problem_id)
contest_scores(contest_id,user_id,score,penalty,version); PK(contest_id,user_id)
execution_leases(submission_id PK,generation,expires_at)Evolve a solution and explain each change
Read the diagram
Run one submission / Persist code and return a submission ID before execution. / Executing untrusted code inside the API process is unsafe.; Use isolated workers / Compile and run in restricted sandboxes with CPU, memory, time, and output limits. / Workers can crash and queues can deliver the same submission twice.; Fence attempts / Lease jobs with attempt tokens and publish a result only for the current attempt. / A stale worker must not overwrite a newer accepted result.
Step 1: Run one submission
Persist code and return a submission ID before execution. Executing untrusted code inside the API process is unsafe.
Step 2: Use isolated workers
Compile and run in restricted sandboxes with CPU, memory, time, and output limits. Workers can crash and queues can deliver the same submission twice.
Step 3: Fence attempts
Lease jobs with attempt tokens and publish a result only for the current attempt. A stale worker must not overwrite a newer accepted result.
Responsibility overview
Read the diagram
Student to Submission API: submit source; Submission API to Result DB: commit submission; Result DB to Job queue: job intent; Job queue to Job dispatcher: lease job; Job dispatcher to Sandbox: run restricted tests; Job dispatcher to Result DB: fenced attempt result
Worked end-to-end scenario
A student submits code and receives s42. The queue delivers s42 to worker W1, which leases attempt 1. W1 stalls after compiling; its lease expires and W2 starts attempt 2. W1 eventually reports success. The result update includes attempt 1, so it is rejected as stale. W2 runs the fixed test-set version under resource limits and commits the authoritative result for attempt 2. The interface polls or receives a notification after commit. Hidden-test contents remain inside the execution boundary; compiler output is capped and escaped before display.
Why these access paths matter
Submission reads use the student and submission ID; execution uses a pinned problem/test-set version and language image. Index queued or expired attempts by availability time. The queue message names durable state, not the only copy of code. Record compile errors separately from resource-limit failures and incorrect answers so the same verdict does not conceal different mechanisms.
Build the baseline first
Read the diagram
Submit API → submission and outbox → work queue; Runner → isolated sandbox → versioned tests; Result commit → status read → user
- Submit API → submission and outbox → work queue.
- Runner → isolated sandbox → versioned tests.
- Result commit → status read → user.
Evolve the design under load
Read the diagram
Fair queues → language pools → bounded sandbox slots; Result events → score projector → sorted ranking; Immutable source and tests → object storage
- Fair queues → language pools → bounded sandbox slots.
- Result events → score projector → sorted ranking.
- Immutable source and tests → object storage.
Defend the hardest decision
Containers share a kernel; a high-assurance judge may use stronger isolation such as microVMs. Apply namespaces, unprivileged identity, syscall restrictions, read-only images, no ambient credentials, disabled egress, and cgroup resource limits. CPU time and wall time catch different abuses. Limit child processes, output size, filesystem usage and compilation time. Test fixtures and expected answers must remain outside user-readable paths.
Failure and recovery analysis
A worker exceeds its lease but continues executing. A new attempt receives a higher generation; the result store accepts only the currently valid generation. Killing the old sandbox limits cost, while fencing prevents stale results from winning. A contest score update must be idempotent by accepted result identity, not by blindly incrementing a score after each queue delivery.
Security and privacy boundary
Never execute learner source in the web server process. Separate judge workers from production secrets and test sandbox escape scenarios before enabling real execution.
Interview follow-ups with reasoning
Question: Why does a Redis sorted set not solve all ranking?
Show answer and explanation
Answer: Tie-break rules, score recalculation, and durable recovery still need authoritative records.
Question: How do you absorb contest bursts?
Show answer and explanation
Answer: Queue fairly, report estimated wait, and cap per-user outstanding submissions.
Question: What if the judge version changes?
Show answer and explanation
Answer: Store problem and judge versions so rejudging is auditable.
Operate and verify the design
Queue wait by language, sandbox startup, resource-limit kills and stale-result rejections.
Pause a runner past lease expiry, start a new attempt and verify that the old result cannot win.
Compare alternatives
| Boundary | Protection | Reason |
|---|---|---|
| User to API | Quota and source limits | Admission control |
| Runner to code | Isolation and resource limits | Untrusted execution |
| Attempt to result | Fencing token | Reject stale completion |
| Test version | Immutable reference | Reproducible judging |
Design workshop: judging and ranking are different pipelines
Core flows are problem lookup, source submission, judged result and contest leaderboard. A production editor and interactive debugger are extensions. Assume result status is observable within five seconds for small tests under admitted load, leaderboard freshness within five seconds and no stale worker can publish a final verdict. A timeout is a verdict only under the pinned execution policy, not a queue-delay measurement.
Store source and signed immutable test manifests in object storage. The admission transaction creates submission(user_id,request_key,request_hash,problem_version,source_key), unique by user/request, and an outbox event. A retry returns the original submission; a changed source under the same key returns 409. Workers fetch the pinned language image/test version. Compile, execute and compare are separate phases with separate time/memory/output budgets. Compilation failure returns a sanitized diagnostic; expected answers never enter user-readable paths. A trusted comparator reads capped output after the sandbox stops. It cannot run an arbitrary contestant-supplied comparator.
Read the diagram
Persist source reference, submission and outbox; Claim attempt generation 5 and lease; Compile then run pinned tests under resource limits; Commit verdict only if generation 5 still current; Accepted-result event with stable identity; Recompute user score; versioned absolute ZADD
For a 90-minute contest choose: more distinct solved problems wins; equal solve counts use smaller sum of first accepted solve times since contest start; remaining ties use user ID. This is a concrete scenario policy, not a claim about LeetCode's actual scoring. With ten problems and durations at most 5,400 seconds, total penalty is at most 54,000. Define score = solved×54,001 + (54,000−penalty). Redis ZRANGE board 0 99 REV returns higher scores first. Equal numeric scores need an explicit user-ID tie policy; Redis reverse lexicographic member order can be selected or an application tie layer can impose ascending IDs.
# Durable score transaction: result delivery can repeat.
if insert_or_lower_first_solve_time(contest, user, problem, accepted_at):
solved, penalty = recompute_from_first_solves(contest, user)
version = increment_score_version(contest, user)
emit_score_event(user, solved * 54001 + 54000 - penalty, version)
# Projection uses Lua: ignore version <= applied version,
# then ZADD the absolute score and store the version atomically.Unique(contest,user,problem) prevents repeated accepted submissions increasing solved count. If an earlier accepted result arrives late, conditionally lower first_solve_time and recompute even when the row exists. Score updates carry per-user versions; an old event cannot overwrite a newer absolute score. Contest/problem ownership makes first-solve timing unambiguous. Store accepted submission time rather than result completion time if the product chooses that rule, and state the cutoff behavior for queued work after contest end.
Read the diagram
User / Solved + penalty / Score and rank; A / 3 solved; 1,200 seconds penalty / 214,803; ahead of B; B / 3 solved; 1,400 seconds penalty / 214,603; behind A; C / 2 solved; 10 seconds penalty / 161,992; behind either 3-solve user; A retry / Same accepted problem delivered again / Unchanged first-solve identity and score
Rebuild a missing leaderboard from first-solves at watermark W, write a new Redis key, replay changes after W and atomically switch the read alias. Database writes and Redis writes are not one transaction; outbox and versioned projection bridge them. At 100,000 viewers polling every five seconds, serve 20,000 reads/s from a cache/replicated read tier rather than aggregate submissions for every read. Partition contests, bound leaderboard pages and report snapshot version. Do not broadcast every score update to every participant unless the product needs it.
Use per-language queues with tenant/user admission quotas and weighted scheduling. Set example budgets such as 2 CPU seconds, 5 wall seconds, 256 MiB, 32 processes and 1 MiB stdout, then tune per problem; these are illustrative limits, not universal safe defaults. Cancellation revokes current attempt ownership and kills the sandbox, but consumed CPU still counts. A stale result fails its conditional commit. Track queue wait separately from compile/run latency.
Exercise: A has an accepted solve at second 500, delivered twice, then an earlier valid solve at second 450 arrives. What changes?
Show answer and explanation
Answer: One solved problem remains. First-solve penalty falls by 50 seconds, a new score version is emitted and the absolute score increases by 50. Blind ZINCRBY on every event would overcount.
Redis sorted sets documents ranking primitives; first-solve semantics, version checks and rebuilds belong to this application.
Requirement trace and verification
| Requirement | Implementing design block | Verification link |
|---|---|---|
| R1 — Complete the primary user journey within the stated scope. | P01–P04; P09–P10. | V1 — Exercise the successful journey and the excluded/boundary case stated in the scope. |
| R2 — Preserve the problem's authoritative data invariant under concurrency. | P05–P06; P11–P12; DD1. | V2 — Run the race/replay case below and assert the invariant at the durable authority. |
| R3 — Meet the declared scale and API behavior without unbounded work. | P05–P08; P13–P14; relevant overload deep dives. | V3 — Exercise the API errors/retry identity and recompute the workload assumption that drives partition or capacity. |
| R4 — Recover from the named failure without losing or duplicating the user-visible outcome. | P15–P16; deep dives; Operations contract. | V4 — Replay accepted submissions, kill a worker, submit an infinite loop, and restart ranking storage; no double credit or cross-submission data leakage. |
Operational contract
| Area | Decision and signals |
|---|---|
| SLIs | Queue wait by language; sandbox startup; timeout/OOM rate; stale attempt results; leaderboard projection lag. |
| Overload limits | Cap queued submissions, concurrent sandboxes, CPU/memory/process/output/time, and leaderboard reads. |
| Recovery | Fence attempts by generation; requeue after lease expiry; rebuild rankings from durable accepted-solve records. |
| Access controls | Keep hidden tests and secrets outside the client; use isolated no-network workers with no app credentials. |
| Dominant cost | Sandbox compute and hidden-test storage/egress. |
Senior-level interviewer follow-ups
Question: A contestant cancels while a sandbox process is still running. What stops a late verdict?
Show answer and explanation
Answer: Revoke the attempt generation and kill the process, then require the same current generation at result commit. Cancellation saves future work, while fencing protects correctness even if termination is delayed.
Question: How do you protect hidden tests from the submission and compiler output?
Show answer and explanation
Answer: Pin immutable test manifests outside learner-readable paths, isolate execution without ambient credentials or network egress, cap output, and sanitize compiler diagnostics before display.
A 60-second design answer
I would persist each submission and return its ID before execution. A durable queue feeds isolated, resource-limited runners pinned to a language image and test version. Attempts have leases and generations so stale workers cannot commit. Accepted results update a durable first-solve record; a versioned projector rebuilds the contest leaderboard. I would separate queue delay from compile/run time and test sandbox escape boundaries.
Related concepts to revisit
Managing Long Running Tasks PostgreSQL
Technical references
PostgreSQL transaction isolation and concurrent updates.
Identify which attempt may publish a score
Submission s7 references immutable problem/test/language versions. A runner under epoch 9 stops heartbeating; epoch 10 replaces it. Results from 9 are rejected at publication. One accepted result revision updates the competition score using s7's identity, so redelivery cannot increment the leaderboard twice.
s7: input/test/runtime versions frozen
attempt 9: lease lost
attempt 10: result accepted
late result 9: rejected
score contribution s7: applied onceRead the diagram
s7: input/test/runtime versions frozen; attempt 9: lease lost; attempt 10: result accepted; late result 9: rejected; score contribution s7: applied once
Untrusted code has bounded CPU, wall time, memory, process count, output and storage. Restrict network and syscall surface, use appropriate sandbox/VM/container isolation, and keep secrets and hidden-test material outside the submitted program's accessible environment. A container label alone is not a security proof. Separate trusted test orchestration from the code process and treat output as untrusted data.
Redis sorted sets can serve bounded leaderboard pages under the declared tie/scoring rules; authoritative results enable repair. Polling every five seconds for 100,000 users is about 20,000 requests/s before caching, so coalesce/cache eligible competition snapshots and bound runner admission separately.
Show answer and explanation
Answer: Reusing s7 dedupes result/score publication, while the attempt epoch decides current ownership. These solve different replay and stale-worker failures.
Decision review: LeetCode
LeetCode: What it is
A judging service accepts a submission and produces a result under pinned language, runtime and test-suite versions.
LeetCode: Why it is needed
User code is untrusted and may consume resources or attack the execution boundary; a timed-out API is not proof that execution stopped.
LeetCode: When to use it
Use isolated asynchronous execution for variable or hostile work. Choose isolation strength from the threat model, beyond merely starting a container.
LeetCode: How to use it
Persist a submission and bounded job. Enforce CPU, memory, process, network and output limits at the execution boundary. Publish results only for the current attempt and pinned judge inputs.
LeetCode: Compare an alternative
One sandbox per job improves isolation but costs startup time. Reused workers require a demonstrably safe reset. Stronger isolation may be necessary for hostile tenants and cannot be inferred from a container label.
LeetCode: Worked decision exercise
The following inputs and policies are original exercise assumptions; they are not measurements or undisclosed company behavior.
Attempt 9 runs submission s7 against suite v3, loses its lease, then completes late. Attempt 10 runs the same pinned inputs and publishes. A delayed attempt 9 must not replace its verdict or independently increment a leaderboard.
Read the diagram
Submission s7 pins runtime and test suite v3; Attempt 9 loses lease; attempt 10 is current; Sandbox enforces resources, not the API timeout; Publication rejects a late attempt 9 verdict
How do you distinguish a program timeout from a broken judge worker?
Show answer and explanation
Answer: Record resource termination and infrastructure failure separately. A verdict reflects the pinned test contract; an infrastructure retry uses the same submission identity and a newer attempt epoch. Result fencing must be enforced at publication, while the process tree is actually terminated by the sandbox boundary.
LeetCode: Check the design
Monitor queue wait, per-language execution cost, output truncation, isolation failures and stale publication rejections. Pin the test version so rejudging is explainable.