Skip to content

Repository files navigation

nod3

Practise algorithm interviews out loud. Then see exactly what you said — and what you were typing while you said it.

License: MIT CI Local first Contributions welcome

New here? Start with CONTRIBUTING.md — the thing this project needs most is not code, it is what real interviews taught you.


Why this exists

Most people fail algorithm interviews for a reason that has nothing to do with algorithms: they go quiet for four minutes while implementing, and the interviewer has no idea whether they know what they are doing.

You cannot see yourself doing that. A recording alone will not show it either, because you have to watch the whole thing back to find the gaps.

nod3 records two things and puts them on one clock: what you said, and what you typed. Then it shows you the places where one was happening without the other.

[00:12] SAID    Since it's sorted I can use two pointers instead of a hash map
[00:23] SILENT  coded for 224s without saying anything (+270 −50 chars)
[04:10] SAID    Time is O of n and space is constant

That middle line is the product.

What a session looks like

  1. Pick a problem. The clock starts when you begin. The microphone is what the speech half of the report is made of, but you can also begin without audio — on a machine with no mic, or somewhere you cannot talk — and still get the code, the timings and the tests.
  2. Talk through it exactly as you would with an interviewer. The editor has autocomplete turned off — an interviewer's shared doc does not have it either.
  3. Run the tests when you have something worth checking. Some cases are hidden.
  4. Hit Finish. Your speech is transcribed locally, aligned against every edit, and handed to an interviewer persona that writes you up against a Google-style rubric.

The report gives you a verdict, seven scored dimensions, and findings pinned to the second they happened — click one and the timeline jumps there.

What it measures

These come from the recording, not from a model's impression of it:

Metric What it tells you
Silent coding Time spent typing with nothing said. The single most common reason a correct candidate gets downgraded.
Planning window Gap between your first word and your first keystroke. Negative means you started typing before you had an approach.
Talk ratio Share of the session you were actually speaking, measured from the waveform.
Longest silence The worst single gap, and when it started.
Churn Characters deleted over characters written. Above ~0.6 usually means a wrong path taken and rewritten.
Test runs How many, and how long before the first one.

The model reads these as facts and argues from them. It does not invent them.

Talking it through afterwards

A verdict tells you how it went. It does not answer the question you actually have afterwards, which is usually "so what should I have done instead". The report ends in a conversation with the same interviewer, still holding the same record — your transcript, your keystrokes, your code, and the answer key it was never allowed to show you during the round.

YOU     Why is my approach worse than the optimal one?
        At 02:40 you committed to a hash map before checking whether the input was
        sorted — the statement says it is. That is the whole difference between
        O(n) space and O(1): two pointers walking inward do the same job...

The round is over, so nothing is withheld. Ask for the optimal solution outright and you get working code in your language. Ask it to drill you and it escalates to the follow-up a real interviewer would have reached for. Every finding in the report has an Ask about this link that starts the conversation there.

The thread is saved to data/sessions/<id>/chat.jsonl and is still there when you come back to the report.

Languages

The six Google lets you interview in:

Language Needs Notes
Python python3 Ships with macOS
JavaScript nothing Runs on the Node that serves the app
TypeScript nothing Node erases the annotations — no compiler
Java brew install openjdk LeetCode-style class Solution
C++ xcode-select --install C++17, common headers preincluded
Go brew install go Your own import block, tab-indented

On first run the app asks which one you interview in, and remembers. Change it any time from the header. Install only the ones you plan to use — the rest are greyed out with the command that would fix them; npm run doctor reports the same.

TypeScript is worth calling out: types are stripped at run time rather than compiled, so annotations cost nothing and there is nothing to install. enum and namespace need real code generation and are refused with an explanation.

Python and JavaScript stubs are written per problem; Java, C++ and Go stubs are generated from the problem's declared type signature, so a new problem does not mean hand-writing five variants:

public int[][] mergeIntervals(int[][] intervals) {
func mergeIntervals(intervals [][]int) [][]int {

Install

Requires macOS or Linux, Node 22.13+ — that is where the type stripping the TypeScript harness runs on landed — and about 500 MB for the speech model.

git clone https://github.com/ostapondo/nod3.git
cd nod3
npm install

# Speech recognition, running entirely on your machine
brew install whisper-cpp ffmpeg      # Linux: apt install ffmpeg, build whisper.cpp
npm run setup:model                  # downloads ggml-small.bin (~488 MB)

npm run doctor                       # verifies every dependency, tells you how to fix gaps
./scripts/start.sh                   # or double-click nod3.command in Finder

start.sh checks the toolchains, installs what npm needs, downloads the model on first run, builds if anything changed, boots both processes and opens the browser. Ctrl-C stops everything and verifies the ports were actually released.

npm run doctor is worth running first. It checks ffmpeg, whisper.cpp, the model, Python and the analysis engine, and prints the exact command to fix anything missing.

The write-up engine

The debrief needs a capable model. Three ways to provide one:

ANALYSIS_ENGINE Requires Notes
claude-code (default) the claude CLI, already signed in No API key. Uses the subscription you have.
anthropic ANTHROPIC_API_KEY Direct Messages API.
ollama a running Ollama server Fully offline, including the write-up. Quality depends on the model.
ANALYSIS_ENGINE=ollama OLLAMA_MODEL=qwen2.5-coder:14b npm run dev

Privacy

Audio, transcripts, code and reports are written to data/sessions/<id>/ in this repo, and your preferences to data/settings.json. Nothing is kept in browser storage, so a cleared profile or a different browser loses nothing. data/ is gitignored.

Speech recognition is fully local — whisper.cpp on your CPU/GPU, no network call.

The one thing that leaves your machine is the debrief prompt, and only when ANALYSIS_ENGINE is claude-code or anthropic. That prompt contains the problem, your transcript, your code and the measurements — but no audio. Every question you ask in the debrief conversation sends the same record again, along with the thread so far. Set ANALYSIS_ENGINE=ollama to keep all of it local.

Configuration

Every option is an environment variable with a working default.

Variable Default Purpose
PORT 4000 API port
NOD3_DATA ./data Where sessions are stored
WHISPER_BIN whisper-cli whisper.cpp binary
WHISPER_MODEL models/ggml-small.bin Speech model. ggml-base.bin is ~3× faster and noticeably worse at technical vocabulary.
WHISPER_LANG en auto to detect
ANALYSIS_ENGINE claude-code claude-code · anthropic · ollama
ANALYSIS_MODEL sonnet Model for the debrief
PYTHON_BIN python3 Interpreter for Python submissions

Adding problems

The seed bank is six problems — three easy, two medium, one hard — kept deliberately small and chosen so that between them they use every type the Java, C++ and Go code generators have to handle. Problems live in apps/server/src/data/problems.json. Add an entry, then add a reference solution to scripts/verify-problems.py — CI re-derives every expected answer from it and fails if any disagree. A problem bank with a wrong answer in it teaches the wrong thing, so this check is not optional.

Each problem carries the ambiguities a strong candidate should raise, the known pitfalls, and the follow-ups an interviewer escalates to. All three feed the debrief.

Architecture

apps/web      Next.js 15 · React 19 · Tailwind 4 · Monaco
apps/server   Express · WebSocket · whisper.cpp · local test runner
data/         your sessions and settings (gitignored)

The browser holds a WebSocket to /ws on the API port. The pipeline pushes each step as it happens, so the review screen shows real progress rather than a spinner, and the connection badge tells you the moment the local server stops answering — a dead backend used to be invisible until the session ended. HTTP still serves requests and uploads, and the UI falls back to polling if the socket cannot be established.

The pipeline after you hit Finish:

audio.webm ──ffmpeg──▶ 16 kHz wav ──┬──▶ silencedetect ──▶ measured speech intervals
                                    └──▶ whisper.cpp   ──▶ timestamped transcript
                                                              │
events.jsonl (keystroke deltas, runs, pastes) ────────────────┤
                                                              ▼
                                              metrics + one merged narrative
                                                              │
                                                              ▼
                                                    interviewer debrief

Everything upstream of the debrief writes its artefact to disk before the next step runs, and the debrief itself reads those artefacts back rather than taking them as arguments. That is what makes the write-up re-runnable: it is the only step that depends on a model and therefore the only one that fails for reasons that have nothing to do with you, so a failed report offers to run again instead of costing you the interview. The same artefacts are what the debrief conversation is grounded in.

Speech timing is measured from the waveform rather than taken from whisper's segment boundaries. Those boundaries stretch across pauses — early on they reported a recording that was 45% silence as 69% talking, which would have made the headline metric a lie.

Development

./scripts/start.sh   # one command: check, build, boot, open
npm run dev          # server + web with hot reload
npm test             # unit tests, language runners, problem-bank verification
npm run test:e2e     # Playwright, boots the real stack
npm run typecheck
npm run lint
npm run doctor

Commits are checked by commitlint (Conventional Commits), staged files by lint-staged, and git push runs typecheck and tests. See CONTRIBUTING.md.

Contributing

Contributions of every kind are welcome here, and most of the ones this project needs are not code.

If you have ever interviewed someone, or been interviewed recently, you know things this tool cannot work out on its own. A waveform can prove that someone was silent for 224 seconds. It cannot say whether that silence cost them the offer, or whether the interviewer had already stopped listening. That only lives in people who were in the room — and the rubric, the debrief persona and the problem bank all get better the moment someone writes it down.

You do not need to open a pull request for any of this:

If you… Do this
have interviewed candidates Interview experience — prose is perfect
interviewed somewhere recently Same template. What surprised you, versus what this tool told you to expect
saw a number that felt wrong Wrong measurement
know a problem the bank should have Suggest a problem — no JSON required
could not get it running It would not start — that is our bug
got lost in a paragraph of these docs Open an issue with the sentence. If it needed re-reading, it needs rewriting
want to write code CONTRIBUTING.md, or an issue saying what interests you

No question here is too basic, draft PRs are welcome, nobody will comment on your English, and a review asking for changes is the normal path rather than a rejection. Full details, including what is deliberately out of scope, are in CONTRIBUTING.md; how we treat each other is in CODE_OF_CONDUCT.md.

One thing to hold onto: never attach session audio to an issue. It is your voice, and it is never needed to debug anything.

Limitations

  • Transcription is not instant. Roughly a third of session length on Apple Silicon, so a 35-minute session takes about 10 minutes to process.
  • No live interviewer. The session is a silent recording by design; there is nobody interrupting you with follow-ups yet.
  • Not a substitute for mock interviews with humans. It cannot reproduce the stress of a real person watching. It is for volume — twenty reps a week of talking while you think.

License

MIT © ostapondo

About

Local-first algorithm interview trainer. Think out loud, then see your reasoning lined up against what you actually typed.

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages