Skip to content

Code Execution & DSA Engine: Judge0 & Code Execution ​

Part 1 of 5 in the Code Execution & DSA Engine series.

Task 1 documented the infrastructure underneath Ediky, and Task 2 documented the UI layer students touch every day. This series covers what happens the moment a student presses Run or Submit — how a block of source code becomes a verdict. Code Execution (Judge0) is the canonical page for why Judge0 is self-hosted and stays authoritative on that; this page goes one layer deeper, into the client code that actually talks to it.

One judge, two entry points ​

src/lib/judge0.js exposes a single runCode() for one program against one stdin, and two batch wrappers built on top of it: runTests() (sequential) and runTestsParallel() (a small worker pool). Both the "Run" button (visible sample cases) and a graded Submission funnel through the same runCode() — the only difference is which wrapper calls it and with what flags.

javascript
// Simplified real shape of runTestsParallel (judge0.js)
async function runTestsParallel({ language, source, tests, concurrency = 1, failFast = false }) {
  let next = 0, stop = false;
  async function worker() {
    while (!stop) {
      const i = next++;
      if (i >= tests.length) return;
      const result = await runCode({ language, source, stdin: tests[i].stdin });
      const judged = judgeCase(result, tests[i].expected);
      if (failFast && !judged.pass) stop = true; // submissions only
    }
  }
  await Promise.all(Array.from({ length: concurrency }, worker));
}

failFast is what separates the two callers: "Run" always reports every visible case so a student can see all of them fail or pass together; a Submission stops scheduling new cases after the first failure, since the shared Oracle VM is a single free-tier box, not an elastic cluster. Benchmarking showed the box is effectively single-core, so production runs at concurrency = 1 regardless — parallel dispatch just added contention and spurious timeouts.

The submission path ​

diagram100%
rendering diagram…

drag to pan · ctrl / ⌘ + scroll to zoom

Graded submissions add a queue in front of this: the write lands in Firestore, a cron-driven judging Worker claims it, and only then does this same runCode() path run — see Code Execution (Judge0) for that outer loop. This page's diagram is the inner loop shared by both paths.

Turning a Judge0 status into a verdict ​

Judge0 CE reports a numeric status id, not a verdict Ediky's UI understands directly. verdictFromStatusId() maps it, and a second pass reclassifies memory-adjacent runtime errors as MLE — Judge0 has no dedicated "Memory Limit Exceeded" status of its own for every runtime, so a crash whose peak memory lands within 3% of the enforced limit is treated as MLE instead of a generic runtime error.

Judge0 status idMeaningEdiky verdict
3Acceptedok (pending output comparison)
4Wrong Answerwrong_answer
5Time Limit Exceededtle
6Compilation Errorcompile_error
7–12Runtime Error (SIGSEGV, SIGXFSZ, SIGFPE, abort, NZEC, other)runtime_error (or mle if peak memory ≥ 97% of the enforced limit)
13–14Internal / Exec Format Errorinternal_error
javascript
// Simplified real shape of shapeResult's MLE reclassification (judge0.js)
function shapeResult(data, memoryLimitMb) {
  let verdict = verdictFromStatusId(data.status.id);
  const memMb = toMb(data.memory);
  if (verdict === "runtime_error" && memMb >= memoryLimitMb * 0.97) {
    verdict = "mle";
  }
  return { verdict, timeMs: toMs(data.time), memoryMb: memMb };
}

Note that status id 3 is not itself a pass — it means the program ran and produced output. Whether that output is correct is a separate step, judgeCase(), which compares normalized stdout against the stored expected string. That separation matters: a program can be "Accepted" by Judge0 (ran cleanly, no crash, no timeout) and still be Wrong Answer by Ediky's own comparison.

One comparison function, not two ​

An earlier version of the app normalized output differently on the Run path than on the Submit path — Run additionally stripped leading whitespace, Submit didn't — so a program with a stray leading blank line could pass locally and fail on grading. normalizeOutput() is now the single function both paths call: it strips trailing whitespace per line and drops leading/trailing blank lines, but leaves internal spacing and case untouched, matching standard competitive-judge behavior.

The request deadline ​

requestTimeoutMs() derives a hard client-side abort deadline from the problem's actually enforced limits rather than a flat constant, so a legitimately slow-but-progressing case is never preempted early: CPU time is clamped to 15s server-side, wall time is cpu + 5s capped at 30s, compilation gets its own 15s budget, and the worst case (compile once + run + one TLE retry) sets the deadline — clamped between a 25s floor and a 90s ceiling. Without this, a hung request to the shared Judge0 box could leave a submission stuck in "Running" forever, since the judging Worker's own heartbeat keeps writing on its own schedule regardless of whether the case itself is making progress.

Self-assessment ​

Word count for this page: 887 words.


→ Next: Test Case Generation & Verification

Ediky Workflow — internal engineering documentation.