System Design · Interview Deep Dive

Design an Online Judge like LeetCode / Codeforces

Run untrusted code from millions of strangers safely, fairly and fast: sandboxing, submission queues, worker isolation, contest-day spikes, leaderboards and plagiarism checks.

✍️ Pranjal Jain, Ex-Microsoft · IIT Kanpur 📅 10 October 2026 ⏱ 18 min read
Sealed sandbox cubes running code submissions on a conveyor with verdict marks and a contest leaderboard
TL;DR
  • Accept submissions fast, then judge asynchronously: API → submission store → priority queue → judge workers → verdict store → push to client.
  • Every run is untrusted code: no network, read-only FS, non-root, cgroup CPU/memory/pids limits, seccomp syscall filter, output limits, wall-clock kill. Use gVisor or microVMs for stronger isolation than plain containers.
  • Measure CPU time on dedicated, identical cores; enforce wall time too; rerun borderline TLEs.
  • Contests are flash-sale traffic: pre-scale, prioritise contest queues, cache test data on workers, pretests during contest and full tests after, efficient leaderboards.
  • Plagiarism: normalise, tokenise, winnow fingerprints (MOSS-style), flag high-overlap pairs, humans decide.

An online judge is one of the most fun system design questions because it contains a genuinely dangerous component: you are building a service whose job is to run arbitrary code written by strangers. A forkbomb, an infinite loop, a 10 GB allocation, a reverse shell, an attempt to read other users' solutions; your design must survive all of it while giving fair, repeatable timings to honest users.

Indian engineers know these platforms well: LeetCode, Codeforces, CodeChef, and the HackerRank/HackerEarth-style assessments used in campus and off-campus online tests. This guide designs one end to end, covering the sandbox in real depth.

Step 1: Requirements

Functional

  • Browse problems; submit code in many languages (C++, Java, Python, Go, JavaScript, Rust…).
  • "Run" against sample or custom input, and "Submit" against hidden test cases.
  • Return a verdict: Accepted, Wrong Answer, Time Limit Exceeded, Memory Limit Exceeded, Runtime Error, Compilation Error, with runtime and memory.
  • Timed contests with a live leaderboard; rejudging when tests change.
  • Submission history; plagiarism detection for contests.

Non-functional

  • Security: user code must not escape the sandbox, reach the network, read other submissions or harm the host.
  • Fairness and determinism: the same code should get the same verdict and similar time on every run.
  • Latency: practice verdicts in a few seconds; acceptable to degrade slightly under contest load, but never lose a submission.
  • Burst handling: contest traffic is spiky, especially at the start (page loads) and in the final minutes (submissions).
  • Availability of the submit endpoint during contests is critical; judging can lag briefly if queued.

Step 2: Capacity Estimates

QuantityAssumption / mathResult
Practice submissions2 million/day ÷ 86,400~23/s average
Contest participantsLarge weekend contest30,000
Contest submissions30k × 4 problems × 2.5 attempts300,000 in 2 hours
End-of-contest burst20% in the last 10 minutes = 60k / 600 s~100 submissions/s
Judge cost per submissionCompile ~1 s + 40 tests × ~0.1 s average~5 CPU-seconds
CPU needed at burst100/s × 5 s~500 cores busy
Machines8 judging cores per machine, 2x headroom~125 machines at peak, far fewer off-peak
Code storage2M/day × 3 KB~6 GB/day
Test data5,000 problems × ~50 MB~250 GB, cached on workers

The judge fleet must stretch by roughly an order of magnitude between a quiet weekday and the last ten minutes of a big contest. That screams queue plus autoscaled workers, with pre-scaling because autoscaling reacts too slowly to a 10-minute spike. If you want to sharpen this kind of estimate, see back-of-the-envelope estimation.

Step 3: API Design

HTTP API
POST /v1/submissions
  { "problem_id": "two-sum", "language": "cpp17", "code": "...",
    "contest_id": "weekly-412", "mode": "submit" }      // or "run" with custom_input
  -> 202 Accepted { "submission_id": "s_01J9ZK..." , "status": "QUEUED" }

GET  /v1/submissions/{id}
  -> { "status": "JUDGING", "progress": "test 17/40" }
  -> { "status": "DONE", "verdict": "WRONG_ANSWER", "failed_test": 23,
       "time_ms": 412, "memory_kb": 18240 }

GET  /v1/submissions/{id}/events      (Server-Sent Events: QUEUED → COMPILING → JUDGING → DONE)
GET  /v1/contests/{id}/leaderboard?page=1&size=50
GET  /v1/contests/{id}/leaderboard/me

The submit call returns 202 with an ID immediately after the code is durably stored and enqueued. The client then follows progress over SSE or polling. Never hold an HTTP request open for the whole judging process; a slow judge would exhaust API server connections exactly during the contest peak.

Step 4: Data Model

SQL
CREATE TABLE submissions (
    submission_id   VARCHAR(32) PRIMARY KEY,
    user_id         BIGINT      NOT NULL,
    problem_id      VARCHAR(64) NOT NULL,
    contest_id      VARCHAR(64),
    language        VARCHAR(16) NOT NULL,
    code_ref        VARCHAR(255) NOT NULL,   -- object storage key, code is not in the row
    status          VARCHAR(16) NOT NULL,    -- QUEUED, COMPILING, JUDGING, DONE
    verdict         VARCHAR(24),
    failed_test     INT,
    time_ms         INT,
    memory_kb       INT,
    testset_version INT,                      -- which tests it was judged against
    created_at      TIMESTAMP   NOT NULL
);
CREATE INDEX idx_sub_user ON submissions (user_id, created_at DESC);
CREATE INDEX idx_sub_contest ON submissions (contest_id, problem_id, created_at);

-- problems: statement, limits per language, checker type, current testset_version
-- testsets: (problem_id, version) -> object storage prefix + content hash

Storing testset_version on every submission makes rejudging precise: when tests are fixed, find all accepted submissions on older versions and requeue them.

Step 5: High-Level Architecture

Browser ──► CDN (problem pages, static assets) │ ▼ ┌─────────────┐ store code ┌──────────────────┐ │ API servers │───────────────►│ Object storage │◄── test data (versioned) │ (rate limit,│ └──────────────────┘ │ │ auth) │ insert row ┌──────────────────┐ │ │ │───────────────►│ Submissions DB │ │ └──────┬──────┘ └────────▲─────────┘ │ │ enqueue │ verdicts │ cached ▼ │ ▼ ┌──────────────────────────┐ ┌─────────┴───────────────────────────┐ │ Queues by priority: │──►│ Judge workers (autoscaled) │ │ contest-live (high) │ │ ┌──────────┐ ┌──────────┐ │ │ practice (normal) │ │ │ sandbox │ │ sandbox │ 1 per │ │ rejudge/batch (low) │ │ │ (gVisor) │ │ (gVisor) │ core │ └──────────────────────────┘ │ └──────────┘ └──────────┘ │ └──────┬──────────────────────────────┘ │ verdict events ▼ ┌──────────────────────────┐ │ Results bus (Kafka/Redis)│──► SSE/WebSocket push └──────────┬───────────────┘──► Leaderboard service └──► Plagiarism batch (post-contest)

Deep Dive 1: The Judging Pipeline

  1. Dequeue. A worker pulls a job (submission ID, language, problem, testset version) from the highest-priority non-empty queue. It acknowledges only after writing the verdict, so a crashed worker's job is redelivered (at-least-once; judging is idempotent because the verdict is a pure function of code and tests).
  2. Fetch. Download the code; ensure test data for (problem, version) is in the local cache, keyed by content hash.
  3. Compile inside a sandbox with its own limits (compilers can be attacked too: a C++ template bomb can consume gigabytes). Compilation errors return immediately.
  4. Run tests one by one, each in a fresh or reset sandbox, feeding input on stdin and capturing stdout to a size-limited buffer.
  5. Check output. Exact or token-wise comparison by default; floating-point tolerance or a special checker program for problems with many valid answers (any valid path, any permutation). Checkers are trusted code but still run with limits.
  6. Stop early at the first failing test for "Submit" (most platforms report the first failure), unless the problem uses partial scoring like IOI-style subtasks.
  7. Record verdict, max time and max memory across tests; publish an event.

A teaching sketch of resource-limited execution

The Python below shows how CPU, memory, file size and process limits map to Linux setrlimit. It is not a secure sandbox by itself (there is no network, filesystem or syscall isolation), but it makes the verdict logic concrete.

Python (Linux)
import resource
import signal
import subprocess

OUTPUT_LIMIT = 16 << 20    # 16 MB of stdout at most


def _limits(cpu_s: int, mem_mb: int):
    def apply():
        resource.setrlimit(resource.RLIMIT_CPU, (cpu_s, cpu_s + 1))          # SIGXCPU, then SIGKILL
        resource.setrlimit(resource.RLIMIT_AS, (mem_mb << 20, mem_mb << 20))
        resource.setrlimit(resource.RLIMIT_FSIZE, (OUTPUT_LIMIT, OUTPUT_LIMIT))
        resource.setrlimit(resource.RLIMIT_NPROC, (64, 64))   # run as a dedicated uid
    return apply


def run_test(cmd: list, input_path: str, expected: str, cpu_s: int = 2, mem_mb: int = 256):
    before = resource.getrusage(resource.RUSAGE_CHILDREN)
    with open(input_path, "rb") as fin:
        try:
            p = subprocess.run(cmd, stdin=fin, capture_output=True,
                               timeout=cpu_s * 3,              # wall-clock guard (sleep, blocked IO)
                               preexec_fn=_limits(cpu_s, mem_mb))
        except subprocess.TimeoutExpired:
            return "TIME_LIMIT_EXCEEDED", None
    after = resource.getrusage(resource.RUSAGE_CHILDREN)
    cpu = (after.ru_utime - before.ru_utime) + (after.ru_stime - before.ru_stime)

    if p.returncode in (-signal.SIGXCPU, -signal.SIGKILL) or cpu > cpu_s:
        return "TIME_LIMIT_EXCEEDED", cpu
    if p.returncode != 0:
        return "RUNTIME_ERROR", cpu          # includes failed allocations under RLIMIT_AS
    if len(p.stdout) > OUTPUT_LIMIT:
        return "OUTPUT_LIMIT_EXCEEDED", cpu
    got = p.stdout.decode(errors="replace").split()
    return ("ACCEPTED" if got == expected.split() else "WRONG_ANSWER"), cpu


# verdict, cpu = run_test(["./a.out"], "tests/07.in", open("tests/07.out").read())

Limitations worth naming in an interview: RLIMIT_AS limits virtual memory, which penalises runtimes like the JVM and Go that reserve large address spaces, so real judges use cgroup v2 memory.max and memory.peak for accurate Memory Limit Exceeded verdicts; and RUSAGE_CHILDREN is process-wide, so concurrent runs need per-sandbox cgroup accounting (cpu.stat).

Deep Dive 2: Sandboxing Untrusted Code

Defence in depth: assume every layer can fail and make the next one stop the attack.

ThreatExampleControl
CPU exhaustionwhile(true);CPU time limit (cgroup cpu.max, RLIMIT_CPU) plus wall-clock kill
Memory exhaustionAllocate 10 GBcgroup memory.max, swap disabled
Fork bombwhile(1) fork();cgroup pids.max (e.g. 64)
Disk filling / huge outputPrint foreverRead-only root FS, small tmpfs, output size cap
Network abuseDownload payloads, crypto mining pools, attack internal servicesSeparate network namespace with no interfaces; workers in an isolated subnet
Reading secrets / other users' code / test answerscat /judge/tests/*.outMount only this run's input; expected output never enters the sandbox; fresh FS per run
Kernel exploitsPrivilege escalation via a syscall bugseccomp-bpf allowlist, non-root user namespace, no capabilities, gVisor or microVM
Side effects between runsLeave a process runningKill the whole cgroup after each run, destroy the sandbox

The building blocks are Linux namespaces (pid, mount, network, user, ipc, uts: what the process can see), cgroups v2 (how much it can use), seccomp-bpf (which system calls it can make) and capabilities (drop all). Tools like nsjail and isolate (used for programming olympiads) package these primitives specifically for judging. Containers are the same primitives with friendlier tooling; the Docker and Kubernetes side is covered in Docker and Kubernetes for backend engineers.

🚫
Never put expected outputs inside the sandboxA common design bug: mounting the whole test directory so the program reads inputs. A malicious submission then simply prints the expected answers. Only the current input enters the sandbox; comparison happens outside.

Deep Dive 3: Worker Isolation: Containers vs gVisor vs microVMs

OptionIsolation boundaryStartupOverheadFit
Plain container (runc) + seccompShared host kernelFast (sub-second)LowestAcceptable with strict seccomp; a kernel bug is a host compromise
gVisor (runsc)User-space kernel intercepts syscallsFastSyscall-heavy code slower; pure computation near nativeStrong default for judging; CPU-bound programs barely notice
Firecracker / Kata microVMHardware virtualisation, own kernelVery fast for a VM (designed for serverless)Memory per VM, more ops workHighest isolation; good for multi-tenant "run anything" (projects, notebooks)
Full VM per submissionHypervisorTens of secondsHighToo slow; use pools of VMs hosting many sandboxes instead

A practical design: judge hosts are VMs in an isolated network with no access to production databases; each host runs one sandbox per physical core using gVisor or a hardened container runtime; and hosts are recycled periodically. Even if someone escapes a sandbox, they land on a disposable VM with nothing valuable on it and no route anywhere. Workers pull jobs and push verdicts through narrow, authenticated APIs rather than holding database credentials.

Warm pools

Creating a sandbox per test adds latency. Keep a pool of pre-created sandboxes per language with the runtime already loaded (a warm JVM image, Python interpreter), and reset or replace them after each run. For Java, snapshotting a warmed JVM, or at least keeping compiler containers warm, cuts seconds off each submission.

Deep Dive 4: Fair and Repeatable Time Limits

Users will compare runtimes to the millisecond, and a borderline solution flipping between Accepted and TLE on resubmission destroys trust.

  • Measure CPU time, not wall time, for the limit. Wall time depends on scheduling noise. Still enforce a wall limit (say 3x) to kill programs that sleep or block on input.
  • One sandbox per physical core, pinned with cpusets, no oversubscription. Hyperthread siblings share execution units, so many judges disable SMT or leave sibling threads idle.
  • Homogeneous hardware: a single instance type for the judge pool, with CPU frequency scaling fixed where possible.
  • Rerun near the limit: if time is within ~10% of the limit, rerun once and take the minimum.
  • Language handling: either per-language limits (Python gets more time) or one limit and let users choose; document the policy. Exclude JVM startup by measuring after a warm-up where possible.
  • Test-data I/O: put input on tmpfs so disk speed does not affect timing.

Deep Dive 5: Handling Contest Load

A contest is a scheduled flash sale: you know exactly when the spike comes. The same playbook used for e-commerce sales applies (see flash sale system design).

  1. Pre-scale. 30 minutes before the start, scale the judge fleet to the expected peak and warm sandbox pools. Reactive autoscaling would add machines after the burst is over.
  2. Pre-cache test data. Push contest test sets (hidden until start) to every worker in encrypted form, and release the key at contest start. No thundering herd on object storage at T+0.
  3. Static problem pages. Render problem statements to static HTML and serve via CDN at the start time, so 30,000 simultaneous page loads never hit app servers.
  4. Priority queues. Contest submissions go to a high-priority queue; practice and rejudges wait. Within the contest queue, cap in-flight submissions per user (for example 2) so one person spamming submit cannot hog workers.
  5. Rate limit submissions per user per minute at the API (token bucket, see rate limiter design).
  6. Pretests vs system tests. Codeforces-style rounds judge only a subset of tests (pretests) during the contest and run the full system tests after it ends. This cuts contest-time load drastically and is a smart point to raise.
  7. Graceful degradation. If the queue grows, keep accepting submissions (they are durable) and show queue position; extend the contest if judging was badly delayed. Never drop submissions.

The queue itself can be Kafka, RabbitMQ, SQS or Redis Streams. Priority queues and per-user fairness are easier in RabbitMQ or with separate queues per priority; the trade-offs are in Kafka vs RabbitMQ vs SQS.

Deep Dive 6: The Contest Leaderboard

In ICPC-style scoring, rank is by problems solved (descending), then penalty (ascending): for each solved problem, minutes from contest start to the accepted submission plus a fixed penalty (commonly 20 minutes) for each earlier rejected attempt on that problem.

  • On each verdict event, the leaderboard service updates the user's per-problem state and recomputes their score, then writes to a Redis sorted set with a composite score such as solved × 107 − penalty_minutes, so a single ZREVRANGE gives the top 50 and ZREVRANK gives a user's rank in O(log n).
  • Ties need a deterministic tiebreak (time of last accepted submission), encoded into the score's lower digits.
  • Serve the top pages from a cache refreshed every few seconds; tens of thousands of users refreshing the standings should not each hit Redis.
  • Freeze the public board in the final hour (an ICPC tradition) by snapshotting it, while still updating internally.
  • After system tests or plagiarism removals, rebuild the leaderboard from the submissions table, which is the source of truth.

The sorted-set approach and its sharding for very large boards are covered in real-time leaderboard system design.

Deep Dive 7: Plagiarism Detection

Copying is common in contests and online assessments, and naive text diffs fail because cheaters rename variables, reorder functions and add comments. The classic approach is MOSS (Measure of Software Similarity, by Aiken et al.), based on winnowing (Schleimer, Wilkerson and Aiken, 2003):

  1. Normalise. Strip comments and whitespace; replace identifiers with a placeholder and numbers with another, keep keywords and operators.
  2. k-grams. Hash every run of k consecutive tokens.
  3. Winnow. Slide a window of w hashes and keep the minimum hash in each window. This picks a small, position-robust set of fingerprints, with the guarantee that any match of at least w + k − 1 tokens shares a fingerprint.
  4. Compare. Index fingerprints per problem (fingerprint → submissions) and count shared fingerprints between pairs; similarity = shared / min(size).
  5. Review. Flag pairs above a threshold for human review with matching regions highlighted.
Python
import re
import zlib

KEYWORDS = {"for", "while", "if", "else", "return", "int", "long", "vector", "cin", "cout"}


def normalize(code: str) -> list:
    """Tokenize, drop comments, and replace identifiers/numbers with placeholders."""
    code = re.sub(r"//.*|/\*[\s\S]*?\*/", " ", code)
    tokens = re.findall(r"[A-Za-z_]\w*|\d+|\S", code)
    return [t if t in KEYWORDS or not (t[0].isalpha() or t[0] == "_" or t[0].isdigit())
            else ("N" if t[0].isdigit() else "V") for t in tokens]


def fingerprints(tokens: list, k: int = 5, w: int = 4) -> set:
    """Winnowing: hash every k-gram, keep the minimum hash in each window of w hashes."""
    grams = [" ".join(tokens[i:i + k]) for i in range(len(tokens) - k + 1)]
    hashes = [zlib.crc32(g.encode()) for g in grams]
    return {min(hashes[i:i + w]) for i in range(len(hashes) - w + 1)}


def similarity(a: str, b: str) -> float:
    fa, fb = fingerprints(normalize(a)), fingerprints(normalize(b))
    return len(fa & fb) / max(1, min(len(fa), len(fb)))


original = """
int main() { int n; cin >> n; long sum = 0;
  for (int i = 0; i < n; i++) { int x; cin >> x; sum += x; }   // add all
  cout << sum; return 0; }"""
renamed = """
int main() { int count; cin >> count; long total = 0;
  for (int j = 0; j < count; j++) { int val; cin >> val; total += val; }
  cout << total; return 0; }"""
different = """
int main() { int n; cin >> n; vector<int> a(n); for (auto &v : a) cin >> v;
  sort(a.begin(), a.end()); cout << a[n / 2]; return 0; }"""

print(normalize("int count = 0; // x"))   # ['int', 'V', '=', 'N', ';']
print(round(similarity(original, renamed), 2), round(similarity(original, different), 2))  # 1.0 0.11
Java
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.*;
import java.util.zip.CRC32;

public class Winnow {
    static final Set<String> KEYWORDS = Set.of("for", "while", "if", "else", "return",
            "int", "long", "vector", "cin", "cout");
    static final Pattern TOKEN = Pattern.compile("[A-Za-z_]\\w*|\\d+|\\S");

    static List<String> normalize(String code) {
        code = code.replaceAll("//.*|/\\*[\\s\\S]*?\\*/", " ");
        List<String> out = new ArrayList<>();
        Matcher m = TOKEN.matcher(code);
        while (m.find()) {
            String t = m.group();
            char c = t.charAt(0);
            if (KEYWORDS.contains(t)) out.add(t);
            else if (Character.isDigit(c)) out.add("N");
            else if (Character.isLetter(c) || c == '_') out.add("V");
            else out.add(t);
        }
        return out;
    }

    static Set<Long> fingerprints(List<String> tokens, int k, int w) {
        List<Long> hashes = new ArrayList<>();
        for (int i = 0; i + k <= tokens.size(); i++) {
            CRC32 crc = new CRC32();
            crc.update(String.join(" ", tokens.subList(i, i + k)).getBytes(StandardCharsets.UTF_8));
            hashes.add(crc.getValue());
        }
        Set<Long> picked = new HashSet<>();
        for (int i = 0; i + w <= hashes.size(); i++)
            picked.add(Collections.min(hashes.subList(i, i + w)));
        return picked;
    }

    static double similarity(String a, String b) {
        Set<Long> fa = fingerprints(normalize(a), 5, 4), fb = fingerprints(normalize(b), 5, 4);
        Set<Long> common = new HashSet<>(fa);
        common.retainAll(fb);
        return (double) common.size() / Math.max(1, Math.min(fa.size(), fb.size()));
    }

    public static void main(String[] args) {
        String a = "int main(){int n;cin>>n;long s=0;for(int i=0;i<n;i++){int x;cin>>x;s+=x;}cout<<s;}";
        String b = "int main(){int cnt;cin>>cnt;long tot=0;for(int j=0;j<cnt;j++){int v;cin>>v;tot+=v;}cout<<tot;}";
        System.out.println(similarity(a, b));   // 1.0: identical after normalisation
    }
}

Dry run

The renamed version differs in every variable name and drops the comment, but after normalisation both programs become the same token stream: int V ( ) { int V ; cin >> V ; long V = N ; for ( int V = N ; …. Every 5-gram matches, so every winnowed fingerprint matches: similarity 1.0. The median-finding program shares only boilerplate like int V ; cin >> V ;, scoring 0.11.

That boilerplate is the false-positive problem in practice: short problems have few sensible solutions, and many people use the same templates (fast I/O macros, common snippets). So exclude fingerprints that appear in a large fraction of submissions or in known public templates, set thresholds per problem, and require human review before penalties. Plagiarism checks run as a batch job after the contest, never on the judging critical path. Comparing all pairs is O(n²), so use the fingerprint inverted index to compare only submissions that share at least one rare fingerprint.

Bottlenecks and Trade-offs

DecisionOption AOption B
IsolationContainers: fast, lower security margingVisor/microVMs: safer, some overhead
Sandbox lifecycleFresh sandbox per test: clean, slowerReset warm sandbox: faster, must guarantee full cleanup
Contest testingFull tests live: final verdicts immediatelyPretests then system tests: far less live load
Result deliveryPolling: simple, more requestsSSE/WebSocket: instant, connection state to manage
CapacityAlways-on peak fleet: simple, expensiveScheduled pre-scaling + autoscaling: cheaper, needs a contest calendar

Scheduled rejudges, plagiarism batches and the system-test phase are all deferred work that fits a job scheduler; see distributed job scheduler design.

What Interviewers Look For

Signal 1

Security depth

You list concrete threats (fork bombs, network, reading answers, kernel exploits) and a control for each.

Signal 2

Async design

202 + queue + workers + push, with at-least-once job handling and idempotent judging.

Signal 3

Fairness

CPU time, pinned cores, identical hardware, reruns near the limit.

Signal 4

Contest realism

Pre-scaling, priority queues, pre-cached tests, pretests, leaderboard design.

Common mistakes

  • Running code directly on the API server, or in a container with network access.
  • Synchronous judging inside the HTTP request.
  • Mounting expected outputs inside the sandbox.
  • Using wall-clock time on shared, oversubscribed machines and calling it fair.
  • Relying on autoscaling alone for a contest that starts at a known minute.
  • Text-diff plagiarism detection that renaming variables defeats.

If you are preparing for this question, it helps to have been on the other side: solving timed contests teaches you exactly what users expect from verdicts and leaderboards. Pair this design with steady DSA practice and you will be comfortable with both rounds.

Frequently Asked Questions

How does LeetCode run user code safely?▾
Platforms do not publish every detail, but the standard approach is to run each submission in an isolated sandbox with no network access, a read-only file system, a non-root user, strict CPU, memory, process and output limits, and a restricted set of allowed system calls. Stronger isolation uses gVisor or lightweight virtual machines such as Firecracker.
Why does an online judge need a queue?▾
Judging takes seconds of CPU per submission and traffic arrives in bursts, especially at the end of contests. A queue decouples accepting submissions from running them, absorbs spikes, lets workers scale independently, and allows contest submissions to be prioritised over practice runs.
What is the difference between Docker and gVisor for code execution?▾
A normal container shares the host kernel, so a kernel vulnerability reachable from inside could compromise the machine. gVisor puts a user-space kernel between the program and the host, intercepting system calls, which greatly reduces the host kernel attack surface at some performance cost. Firecracker microVMs give each sandbox its own minimal kernel.
How are time limits measured fairly?▾
Judges measure CPU time rather than wall time for the main limit, also enforce a wall-clock limit to catch sleeping or blocked programs, run on identical dedicated hardware pinned to a core, and may rerun submissions that land near the limit. Some platforms apply language multipliers for slower languages.
How do coding contests handle thousands of submissions at once?▾
They pre-scale judge workers before the contest, prioritise contest queues, cache test data on workers ahead of time, run only pretests during the contest with full system tests afterwards on some platforms, and use an efficiently updated leaderboard rather than recomputing ranks on every request.
How is plagiarism detected in programming contests?▾
Code is normalised by removing comments and replacing identifiers, tokenised, and fingerprinted, for example with the winnowing algorithm used by MOSS. Pairs of submissions with high fingerprint overlap are flagged and then reviewed by humans, because common templates and short solutions create false positives.
What verdicts does an online judge return?▾
Typical verdicts are Accepted, Wrong Answer, Time Limit Exceeded, Memory Limit Exceeded, Runtime Error, Compilation Error and Output Limit Exceeded, usually with the first failing test number and the time and memory used.

Master System Design Interviews

Prepflix's system design course covers execution platforms, queues and isolation with mock interviews, and our DSA track gives you the contest practice to match.

Explore the Prepflix Course →
Pranjal Jain
Pranjal Jain

Ex-Microsoft SDE · IIT Kanpur · Founder of Prepflix. Helps engineers crack startup and product company interviews across India.