Practical Coding Rounds/Debugging Unfamiliar Code

Debugging Methodology: Reproduce → Isolate → Hypothesize → Fix

A repeatable four-step loop for any bug-squash round — and the single habit that separates a pass from a fail under time pressure: never changing code you can't yet explain.

!!!3/5Theory: 25m

Why this is scored as a methodology, not a speed test

A bug-squash round hands you a codebase of a few hundred to a few thousand lines — occasionally in a language you're less fluent in, deliberately — with one known but undisclosed bug, and 45-90 minutes on the clock. The temptation is to treat it like a race: read fast, spot the bug, fix it, done. Interviewers who run this format explicitly say otherwise: they're scoring whether you form and test hypotheses systematically, not whether you got lucky and spotted the bug in file three. A candidate who takes 30 minutes to methodically narrow down the cause and fixes it correctly outscores one who guesses right in five minutes and can't explain why the fix works.

The four-step loop

1. Reproduce — before anything else. You cannot debug what you can't reliably trigger. If the bug report is vague ("totals are sometimes wrong"), your first job is turning it into a specific, deterministic, minimal repro: the smallest input that reliably produces the wrong output, expressed as a real assertion — a test, not a scratch print statement you'll delete. This matters for two reasons: it forces you to understand what "wrong" precisely means before you touch anything, and it gives you a fast, repeatable check for every hypothesis you're about to test.

2. Isolate — narrow the search space before reading everything. Once you have a repro, the next move is bisecting the codebase, not reading it linearly top to bottom. Useful narrowing moves, roughly in order of speed:

  • Run the existing test suite — a test that's unexpectedly passing or failing near the bug's symptom is often a faster signal than any manual read.
  • git log/git blame the suspicious file or function — a recent commit touching that exact area is a strong prior, especially if the bug report coincides with a recent release.
  • Add a small number of targeted print/log statements or breakpoints at the boundary between "definitely correct" and "definitely wrong" code, then binary-search that boundary inward.
  • If a regression is suspected against a known-good state, git bisect finds the introducing commit directly rather than reasoning about the diff by eye.

3. Hypothesize — and say the hypothesis out loud before testing it. "I think the discount is being applied to the pre-tax subtotal instead of the post-tax total — let me check where tax gets calculated relative to discount" is a testable, falsifiable claim. Compare that to silently poking around, which gives the interviewer nothing to follow and you nothing to fall back on if you're wrong. Every hypothesis should have an obvious way to confirm or rule it out — if you can't state how you'd know you're wrong, it isn't a hypothesis yet, it's a guess.

4. Fix — the smallest correct change, verified by the repro you wrote in step 1. Once the root cause is confirmed, resist the urge to also "clean up" adjacent code you noticed along the way; that's a different round (see Refactoring & Code Review Rounds) with a different clock. Apply the targeted fix, rerun your repro to confirm it now passes, and rerun the existing test suite to confirm you didn't break anything else — a fix that "obviously works" but that you didn't actually re-verify against the repro is a surprisingly common way candidates lose points in the last five minutes.

The single highest-signal habit: never change code you can't explain

The most consistently cited failure mode across bug-squash retrospectives is speculative changes — tweaking a conditional, adding a null check, or reordering two lines because it "might help," without first confirming that line is actually where the bug lives. This reads as guessing, and it's dangerous for a concrete reason: a speculative change can accidentally mask the symptom (the specific repro stops failing) while leaving the actual root cause in place, which is worse than not fixing it at all — the bug will resurface in production under a slightly different input, and now there's an unexplained, unrelated change sitting in the diff as well. If you're tempted to try something without being sure why, say that out loud explicitly: "I'm not certain this is the cause, but let me try isolating whether the issue is here" — framed as an experiment, not a fix, so the interviewer can see you're aware of the distinction.

What "isolate" looks like when the codebase is in an unfamiliar language

Some bug-squash rounds deliberately hand you a codebase in a language you don't use daily — the skill being tested is explicitly reading comprehension and methodology transfer, not syntax fluency. In that situation, lean even harder on the codebase's own tests (they tell you intended behavior in a language-agnostic way — inputs and expected outputs) and on the standard library's error messages and stack traces, which are usually recognizable across C-family and scripting languages even when the surrounding syntax isn't. It's entirely fair to say "I'm less fluent in this language — let me confirm this syntax does what I think it does" and quickly check the docs; asking is far cheaper than a wrong assumption about, say, whether a slice operation is inclusive or exclusive on the end index.

Reference implementations in:

Turning a Vague Bug Report into a Minimal, Runnable Repro

The shape is identical across languages: isolate the smallest input that still triggers the bug, assert the wrong behavior explicitly, and only then start reading the implementation.

# Bug report: "totals are sometimes wrong for orders with discounts" # Step 1: turn that into a minimal, explicit repro -- not a debugger session yet. from orders import calculate_total def test_repro_discount_bug(): order = {"items": [{"price": 10.00, "qty": 2}], "discount_percent": 10} total = calculate_total(order) assert total == 18.00, f"expected 18.00, got {total}" # fails -- now I have a repro

This is a real, committed test, not a scratch script — it's the thing you'll rerun after every hypothesis, and it's the artifact an interviewer can look at to confirm you actually understood the bug report before touching implementation code.

Further Resources (Optional)