Reps
A code-first practice system for a Python technical interview
LeetCode problems are where interview prep usually starts, and that is the wrong end. Reps treats each problem as the capstone of a skill graph, drills the Python underneath it until it comes out without thinking, and only then asks for the problem, from a blank editor, with real tests.
- Role
- Sole designer and builder
- Status
- Public demo live; the full app is single-user
- Core stack
- TypeScript · Next.js · Pyodide · CodeMirror · IndexedDB · Supabase
- Tests
- 150 unit tests, plus every rep verified in CPython and Pyodide

Engineering brief
- Architecture
- Next.js and React over a typed curriculum: a skill graph, ten day modules and two mock interviews as data. Pyodide runs in a module Web Worker. Progress is local-first in IndexedDB with an outbox, synced to Supabase behind owner-only row-level security.
- The hard part
- Keeping the curriculum honest about practice. An audit classifies all 689 reps by what they make you produce and fails the build if writing and debugging fall below their share, so the material cannot drift into reading about code.
- Testing
- 150 unit tests covering mastery, scheduling, sync and the curriculum guardrails, with the database policies run against real Postgres through PGlite. Every rep’s reference solution is executed in CPython and in Pyodide. Playwright covers a rep end to end and an infinite loop being stopped.
- Known limitation
- Built for one person and one interview window, and the mastery weights are reasoned rather than fitted to outcomes. It measures clean, recent, unaided writing, not interview performance itself.
Try it
Four reps, up to Two Sum, in your browser
The public demo is a slice of the hashing ladder: build a dict in a loop, debug one, combine the moves, then the capstone. Each rep uses the move from the one before. The Python runs in the page against the real tests, and nothing typed is saved or sent anywhere.
It also lays out the plan the app runs now: 15 patterns in the order they build on each other, 499 ladder reps and 68 LeetCode problems over 13 weeks.

The problem
Recognising a solution is not the same as writing one
The format was fixed: two 45-minute rounds, Python, a blank editor, data structures and algorithms. The failure that format punishes is specific. It is not failing to understand a solution when it is shown. It is stalling on the second line because building a frequency map, or moving two pointers, is still a thing that needs thought.
Reading solutions trains recognition. Reps is built on the opposite bet: the primitives have to be written, broken, fixed and recalled cold until they are automatic, and only then is the problem worth attempting.
Curriculum
689 reps, and most of them are writing
The first sprint ran ten topic stages, from hashing to backtracking and interview execution, each a ladder of short reps. A rung asks for one thing: make an empty dict, trace what .get returns, find the line that crashes, then write it, then debug someone else’s. Real problems arrive at the top of the ladder, after the pieces they need.

91% of planned time is active coding, and a test keeps it there.
The audit that produces these numbers runs in the unit suite. A day with too much reading, too few debug reps, or more than three passive reps in a row is a failing test, not a note in a document.
Python in the browser
Real execution, with a way out of every loop
Every code rep runs real Python. Pyodide loads in a module Web Worker the first time a rep needs it, so pages that never run code never pay for it. Tests are graded by the same harness the content verifier uses, and hidden tests report only a count.
A while True cannot be interrupted from the main thread. Each run gets five seconds; past that the worker is terminated, a fresh one is started, and the rep says the loop probably never makes progress. That path has its own Playwright test.
Mastery
A score you cannot earn by recognising things
Mastery per skill is a pure function of attempts. Each attempt is weighted by what it asked for and by whether it was recalled cold, then discounted for hints, retries and a viewed solution. Accuracy is the recency-weighted mean of the last 15 attempts, and evidence saturates as clean reps accumulate.
- Recognise 0.5×
- Trace 0.6×
- Debug 1.15×
- Retrieval 1.25×
- Capstone 1.3×
- Cold rep × 1.5
quality = hint factor × solution factor × retry factor accuracy = recency-weighted mean quality, last 15 attempts evidence = 1 − e^(−Σ weight × quality / 4) score = 100 × accuracy × evidence (capped at 55 on recognition alone)
Recognition alone caps a skill at 55. Fluent needs a score of 85, three clean reps in a row and a clean cold rep, so a skill cannot look finished on the strength of multiple choice.

The day
One plan, recomputed from what actually happened
Today’s page is derived, not stored: the current stage on the road to ready, the reps due, and cold reps resurfacing a day after something was first written. Mastery checks gate what comes after them, so an uncleared check carries onto the next day rather than being skipped.

Interview mode
Rehearsal with the help taken away
Two 45-minute mock interviews run on a timer in a plain editor: no autocomplete, no hints, no AI. You write your own test calls; submit runs the hidden ones. Complexity and approach are written down, the way they would be said out loud. Capstones can also be taken as timed cold solves.
Persistence
Local-first, synced when signed in
With no keys at all, Reps works fully on one device. Signing in adds durable progress across devices. Python never touches the database; only meaningful events do: submissions, hints, solution views, reviews and mock results.
- UI
- Updates optimistically, so submitting a rep never waits on the network.
- IndexedDB
- Instant reload, offline use, and an outbox of writes not yet confirmed.
- Supabase
- The durable copy, pushed after a short debounce and pulled incrementally by server timestamp. A row with a pending local write wins; otherwise the server does.
- Postgres RLS
- Every table requires both the signed-in user and membership in an owners table. The policies are tested on real Postgres in the unit suite, through PGlite.
Engineering decisions
The calls that shaped it
- Problems are capstones, not lessons
- Each canonical problem sits at the top of a skill graph: 73 skills with prerequisites, from `list.append` to graph BFS. The reps underneath train one primitive at a time, so by the time Two Sum appears it is an assembly of things already automatic rather than a puzzle.
- Code-first is enforced by a test, not a promise
- An audit classifies every rep as active, guided or passive by what it makes you produce, then checks the time budget twice: once against authored minutes and once against an independent model, so an inflated estimate cannot pad the numbers. Below 75% active time overall, 65% on any day, or with fewer than four debug reps a day, the test suite fails.
- Python runs in the browser, in a worker
- Pyodide loads in a module Web Worker on first use. Each run has a five-second budget; a runaway loop cannot be interrupted from outside, so the worker is terminated and a fresh one started, and the visitor is told why. The editor never freezes.
- One test harness, run three ways
- The same `harness.py` grades a rep in the browser and verifies the content in CI: every rep’s reference solution and predicted output is run through CPython and again through Pyodide, so a rep cannot ship with an answer the browser would disagree with.
- Mastery is computed, never typed in
- The score is a pure function of attempt history. Nothing writes a mastery number directly, so it is recomputed when attempts sync from another device and can never disagree with the record of what was actually done.
- Generated reps must pass before they are shown
- The optional coach can write a new rep on request. It is validated with Zod, then executed: the canonical solution must pass every test and the starter code must not. One regeneration is allowed; after that the answer is that it could not be generated, rather than a rep that may be wrong.
Limits
What this does not establish
- Built for one person
- Single user by design. It began as a ten-day sprint for one interview and now runs a 13-week plan, ordered by what that interview format rewards, so it is a training plan rather than a general course.
- Mastery is a heuristic
- The weights are reasoned, not fitted. Nothing here establishes that a score of 85 predicts interview performance; it predicts that the skill was written cleanly, recently and from memory.
- The demo is a slice
- The public demo is four reps and the plan, with nothing saved. Mastery, scheduling and sync live in the full app, which anyone can run locally with no keys at all.
A personal tool, built and used by one person. Problem names refer to public LeetCode problems by link only; no problem statements are copied.
