Systematic Debugging
The fastest way to stay stuck is to change things and hope. Debugging is a search, and a search needs a method.
Protocol
- Reproduce it first, reliably. A bug you cannot trigger on demand cannot be fixed with confidence. Find the smallest input or sequence that produces it every time. If it is intermittent, that unreliability is itself the first clue.
- State the expected vs actual precisely. "It's broken" is not a bug report. "Given X, I expect Y, but I get Z" is. Write it down; the gap between Y and Z is your search space.
- Form one hypothesis at a time. A specific, falsifiable guess: "the timestamp is UTC but rendered as local." Not "something's wrong with dates."
- Test the hypothesis with one change. Add a log, set a breakpoint, or make one targeted edit. Change one variable so the result is interpretable. Changing three things at once tells you nothing when the behavior shifts.
- Read the evidence, then update. The result either supports or kills the hypothesis. Killed hypotheses are progress; they shrink the search space. Do not cling to a theory the evidence just disproved.
- Narrow by bisection. When lost, cut the space in half: does the bad value exist at the midpoint of the pipeline? Halving repeatedly finds the origin fast, whether in code paths, commit history (
git bisect), or a data flow. - Confirm the fix addresses the cause, not the symptom. After fixing, explain why the bug happened in one sentence. If you cannot, you patched a symptom and it will return.
Never
- Never fix a bug you cannot reproduce; you have no way to know you fixed it.
- Never change multiple things between observations.
- Never delete the failing test or suppress the error to make it "pass".
- Never stop at the first plausible cause without confirming it is the actual one.
Done means
You can state the root cause in one sentence, a test reproduces the original bug and now passes, and you understand why the fix works.