name: systematic-debugging description: Root-cause a bug with a hypothesis-driven loop instead of shotgun edits. Use when a bug isn't obvious after the first look, or when asked to "find out why" something fails. version: 1.0.0 license: MIT keywords: [debugging, root-cause, methodology, bisect, testing]
systematic-debugging
Never fix what you can't reproduce; never explain what you haven't observed. The loop is: reproduce → observe → hypothesize → test the hypothesis → narrow. One variable at a time.
Procedure
- Reproduce first. Find the smallest, fastest command that shows the
failure deterministically. If it's flaky, make the loop tight
(
while ./repro; do :; done) and treat flakiness itself as a clue (timing, ordering, shared state). - Read the actual error. The full message, the first stack frame in your own code, and the line right before things went wrong. Resist pattern-matching to a familiar failure — verify this one.
- State a falsifiable hypothesis ("the cache returns stale entries after a restart") and pick the cheapest observation that could kill it: a log line, an assertion, a debugger breakpoint, one curl.
- Bisect the space, whichever axis is cheapest:
- history:
git bisect run ./repro - data: half the failing input, recurse
- stack: confirm the bad value at the boundary between two layers, then descend into the guilty one only
- history:
- The fix must explain the symptom. Before writing it, say why this cause produces exactly this behavior. If the explanation is fuzzy, you've found a bug, maybe not the bug.
- Prove it: the repro from step 1 now passes, AND a new regression test
fails without the fix. Then look for siblings — the same mistake usually
exists elsewhere (
grepfor the pattern you just fixed).
Anti-patterns
- Changing two things between observations — you learn nothing from the result.
- "It works now" without knowing why it failed — it will be back.
- Adding sleeps to fix timing issues — that's hiding the race, not fixing it.
- Debugging through the framework before confirming your own code's inputs and outputs at the boundary.