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
A Reps capstone, Two Sum: the prompt, three worked examples and a Why it works note on the left; on the right a one-pass dictionary solution in the Python editor, a panel reading All tests passed 8 of 8 with two visible tests and six hidden ones, and Rep complete, crediting enumerate, index map and dict membership.
A capstone solved in the browser. The Python ran in a Web Worker, and the hidden tests stay hidden.Full size

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 Reps public demo: the heading Build fluency through repetition, counts of 15 patterns, 499 ladder reps and 68 LeetCode problems, and a Try it panel with four steps, Build, Debug, Combine and Capstone. The first rep, Build a dict in a loop, is solved in the editor and reads All tests passed, 3 of 3.
The first rep of the demo, solved. Open the demoFull size

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.

Write code413
Debug / fix132
Predict output72
Explain31
Capstone25
Multiple choice13
Reorder / fill3
The Dictionary ladder in Reps: 30 reps from the ground up, 1 hour 31 minutes, 19 code, 3 debug, 1 guided and 7 recall, listed rung by rung from Make an empty dict and Trace: add and overwrite up to Debug: count the expensive ones, each tagged Choose, Trace, Write or Debug with its minutes.
One ladder. Thirty rungs from an empty dict to real problems, each labelled by what it asks you to produce.Full size

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.

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 Reps Skills page: 36 of 73 skills practised and 13 competent or better, then a grid of Python foundation skills, each card with a score, a bar, a status of Unseen, Introduced, Practicing or Competent, and its record, for example list.append at 80, Competent, 6 attempts, 6 clean writes, 1 fix.
Every number comes from attempt history: how many, how many clean, how many fixes.Full size

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.

The Reps Today page: a Done for today panel with 20 of 20 reps complete, then Road to Ready, stating the finish line and showing ten topic stages from Python plus Hashing to Interview Execution with per-stage counts, 1 of 10 topics complete and 53 of 186 canonical reps.
The finish line is written down, and the ten stages show how far there is to go.Full size

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.

The Reps Interview page: two mock interview cards, each 45 minutes with a Start button, and a list of timed cold solves including Two Sum, Valid Parentheses, Number of Islands, Reverse Linked List and Level order traversal.

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.