The arithmetic, written out.
Nothing here is a secret, and a lock you cannot reason about is a lock you will resent. Every threshold below is the default the code actually ships with.
The lock
The API decides.
You arm a timer. The server records the deadline, and every client renders its countdown from the server's clock rather than the machine's — so moving your system time buys nothing.
The problem is chosen when the timer fires, not when you arm it. Otherwise you could read it during the focus block, which defeats the point.
When both conditions pass, the API signs an unlock token. The desktop shell verifies the signature in its main process and checks that it names the held session. This separates the page from the unlock decision. It does not make the API safe to expose: it has no caller authentication.
The speed gate
Correct, and fast.
The part people find surprising, and the reason a working answer can leave you locked.
budget = ceil(best × 1.35) + 40ms
best = fastest accepted runtime for this problem, in your language
Where your number comes from. Your code runs the whole suite twice. Within one run, the slowest test case is that run's time — a solution is only as fast as its worst input. Between runs, the faster one counts, so a scheduler hiccup does not cost you the session.
Timing happens inside the container. Measuring around docker run would include half a second of image start-up that varies more than the algorithmic difference the gate exists to detect — enough to make an O(n) and an O(n²) answer indistinguishable.
The 40ms floor is not padding. On a problem whose best answer takes 8ms, a 35% tolerance is a 3ms band — narrower than the judge's own jitter. The floor keeps fast problems winnable.
The bar moves. When you submit a faster accepted answer it becomes the new best, and the budget tightens for your future attempts.
- Tolerance
- 1.35 — you may be 35% slower than the best known answer
- Floor
- 40ms — added to every budget
- Runs
- 2 — the faster one counts
- Within a run
- The slowest test case is your time
Difficulty
Two rules, both visible.
- Three fast solves in a row moves you up.
- A solve counts as fast when you finish inside the problem's own average solve time. The streak is consecutive — one slow solve puts it back to zero, and the counter in the app shows exactly where you stand.
- Two failed sessions in a row moves you down.
- Giving up counts as a failure, and so does the desktop kill switch. Easy is the floor; you cannot fall off the bottom.
- Changing tier resets both counters.
- A streak belongs to the tier it was earned at. Arriving at Hard with two fast solves banked would promote you out of it on your first success there.
That is the entire ladder. It is a pure function of your history with no I/O and no model, which is why the interface can always tell you what would happen next rather than showing a score you cannot interrogate.
Data
Where your data lives.
A tool that interrupts your work has to be specific about this. CodeLock can only see its own window — it has no view of your other apps, and no keylogger.
- Submitted code
- Sent to your local API, run in a throwaway container with no network access, and stored in your CodeLock Postgres database.
- Timers, verdicts and progress
- Stored in the same local database, along with submissions and the learning log.
- Unlock proof
- Signed by the API. Verified by the desktop main process against the session being held.
- External services
- No account system, OAuth, GitHub sync or LeetCode connection. No OpenAI requests and no telemetry.
No telemetry and no metered application APIs. Keep the API and judge on your own machine or a trusted private network. CORS is a browser control, not authentication. See the privacy notice for data removal.