03 / open source / 01 of 02

CodeArena, a coding-practice platform that runs in the browser

A LeetCode-style practice site that runs in the browser, with 20 problems, a Monaco editor and runners for JavaScript, Python and C++. It has no backend of its own: JavaScript and Python run in the page, and C++ goes to the public Judge0 API.

What it does

You pick a problem, write a solution and press Ctrl+Enter. Each test case comes back as pass, fail, error or time limit exceeded as it finishes. Code and solved problems are saved in localStorage, so progress survives a reload without an account. Every problem has an editorial.

CodeArena on desktop with the Maximum Subarray problem open: the problem description and examples on the left, a Monaco editor holding a Kadane solution on the right, and a Test Results panel below it reading 6/6 passed and All Test Cases Passed.
Maximum Subarray after Run Code - every test case runs in its own Web Worker and all six pass.

Problems are folders

Each problem is a folder with two files: problem.js for the statement, starter code and function name, and testcases.js for inputs, expected output and how to compare them. A registry finds every folder at build time with Vite's import.meta.glob, so adding a problem means adding a folder.

The glob is eager, so every problem ships in the main bundle. At 20 problems and 106 test cases that is fine. At a few hundred it would not be.

Running JavaScript without freezing the tab

The interesting problem is code that never returns. A while (true) on the main thread freezes the tab, and nothing on that thread can interrupt it. So each test case gets its own Web Worker, created from a Blob URL. Inside it, the user's code is wrapped with the Function constructor and the named function is called with the test input.

mohan-kumar-0/codearena: src/engine/codeRunner.js, lines 171 to 196 codearena/src/engine/codeRunner.js L171-196 171 function runSingleTestInWorker(userCode, functionName, testCase, timeLimit) { 172 return new Promise((resolve) => { 173 const worker = new Worker(workerUrl); 174 let resolved = false; 175 176 const timer = setTimeout(() => { 177 if (!resolved) { 178 resolved = true; 179 worker.terminate(); 180 resolve({ 181 id: testCase.id, 182 status: 'tle', 183 error: `Time Limit Exceeded (>${timeLimit}ms)`, 184 input: testCase.input, 185 expected: testCase.expected, 186 actual: undefined, 187 time: timeLimit, 188 }); 189 } 190 }, timeLimit); 191 192 worker.onmessage = (e) => { 193 if (resolved) return; 194 resolved = true; 195 clearTimeout(timer); 196 worker.terminate();
codearena/src/engine/codeRunner.js, lines 171-196The TLE guard - a main-thread timer terminates the worker, so an infinite loop costs one test case instead of the tab.

The timer lives on the main thread. If the worker has not answered within the limit, 3 seconds by default, the main thread calls worker.terminate() and records Time Limit Exceeded. Terminating a worker kills its thread, and in a browser that is the only dependable way to stop a synchronous loop.

Test cases run one after another. A fresh worker per case costs some start-up time, and in return no state leaks between cases and each result shows as soon as it lands. For in-place problems like Rotate Image, the test checks the mutated argument instead of the return value.

More than one right answer

Some problems accept several answers, so each test case names one of five comparators: exact, deep structural equality, unordered arrays (Two Sum indices in either order), unordered groups (Group Anagrams), and a palindrome check for Longest Palindromic Substring, where any palindrome of the right length passes.

mohan-kumar-0/codearena: src/engine/codeRunner.js, lines 146 to 166 codearena/src/engine/codeRunner.js L146-166 146 function compareResults(actual, expected, compareType) { 147 switch (compareType) { 148 case 'exact': 149 return actual === expected; 150 case 'deepEqual': 151 return deepEqual(actual, expected); 152 case 'unorderedArray': 153 return unorderedArrayEqual(actual, expected); 154 case 'unorderedGroups': 155 return unorderedGroupsEqual(actual, expected); 156 case 'palindromeLength': 157 // actual should be a palindromic string of the expected length 158 return ( 159 typeof actual === 'string' && 160 actual.length === expected && 161 isPalindrome(actual) 162 ); 163 default: 164 return deepEqual(actual, expected); 165 } 166 }
codearena/src/engine/codeRunner.js, lines 146-166Pluggable comparators - each test case names how its answer is checked, so order-free and multi-answer problems grade fairly.

Python and C++, and where they fall short

Python runs through Pyodide, loaded from a CDN, on the main thread. Each run gets a fresh namespace, and if the code defines a Solution class the runner creates one and calls the method on it. There is no time limit. A Python infinite loop freezes the tab, which is exactly the failure the JavaScript runner was built to prevent.

CodeArena problem page for Maximum Subarray before a run: description, examples and constraints on the left, the JavaScript editor on the right with the language picker and Run Code button above it.
The solve view - problem on the left, Monaco editor on the right, Ctrl + Enter to run.

C++ goes to the public Judge0 instance. For each test case the runner generates a main() that declares the inputs with types guessed from the JSON values, calls the method on Solution and prints the result as JSON for the same comparators. Every test case is a separate compile-and-run request to a third-party service, and the per-problem time limit is not sent, so C++ gets whatever limit the public instance applies.

Coverage is uneven. Only 5 of the 20 problems ship Python and C++ starter code. On the other 15 the editor opens empty when you switch language.

Screenshots

CodeArena landing page: the heading Sharpen Your JavaScript Skills, a search box, difficulty and topic filter chips, and the start of the problem table listing Two Sum, Reverse String, Valid Parentheses and Maximum Subarray.
The problem list - every problem folder is picked up at build time and filtered by difficulty or topic.
CodeArena on a phone: the Maximum Subarray description at the top, a short editor pane in the middle, and Test Results below showing 6/6 passed with the first three test cases accepted.
The same run on a 390 px wide phone - description, editor and results stack vertically.

What broke

  • Reading the C++ wrapper again for this write-up, I found that an array of single characters is declared as vector<char> but its elements are written in double quotes, as strings. That does not compile, so Reverse String in C++ should fail with a compilation error whatever the user writes. String inputs are not escaped either.
  • The README and page metadata still describe a JavaScript-only site, written before Python and C++ were added.

What I would change

  • Move Pyodide into a worker and reuse the terminate-on-timeout pattern, so Python gets a real time limit.
  • Put C++ behind a small proxy or a self-hosted Judge0, send the per-problem time limit, and compile once per run instead of once per test case.
  • Finish Python and C++ starter code for the other 15 problems, fix character arrays and escape string literals in the C++ wrapper, and make the problem glob lazy before the list grows.

Questions

Does C++ run in the browser?
No. It is compiled and run by the public Judge0 API. JavaScript and Python run in the browser.
Can a Python infinite loop hang the page?
Yes, today it can. Only the JavaScript runner enforces a time limit.