Structured Debugging
Systematic debugging using the scientific method. No more random changes hoping something works.
Steps
- Reproduce the bug:
- Get the exact error message, stack trace, or unexpected behavior
- Find the minimal reproduction case
- Confirm it’s reproducible (not a flaky test or race condition)
- If it can’t be reproduced, gather more data before proceeding
- Form a hypothesis:
- Based on the error and context, what’s the most likely cause?
- List 2-3 possible causes ranked by probability
- State what you’d expect to see if each hypothesis is correct
- Test the hypothesis:
- Add a targeted log, breakpoint, or assertion to test the top hypothesis
- Run the reproduction case
- Does the evidence support or refute the hypothesis?
- Narrow down:
- If hypothesis is supported: zoom in on the specific code path
- If hypothesis is refuted: move to the next hypothesis
- Use binary search: add a check at the midpoint of the suspected code path
- Each step should cut the problem space in half
- Identify the root cause:
- Don’t stop at the symptom — find why it’s happening
- Check: is this a data issue, logic error, race condition, or configuration problem?
- Verify the root cause explains ALL observed symptoms
- Fix and verify:
- Make the minimum change that fixes the root cause
- Run the original reproduction case — does it pass?
- Run the full test suite — did the fix introduce regressions?
- Remove any debugging artifacts (extra logs, breakpoints)
- Prevent recurrence:
- Add a test case that would have caught this bug
- Consider: should this be in
tasks/lessons.md? - Is there a class of similar bugs that should be checked?
Important
- Don’t change code randomly. Every change should test a specific hypothesis.
- Don’t fix symptoms. A null check at the crash site is a band-aid — find why it’s null.
- Keep notes. Write down hypotheses and results so you don’t re-test the same thing.
- Time-box. If you’re stuck after 3 hypotheses, step back and reconsider assumptions.
- Remove debug artifacts. No console.log, debugger statements, or commented-out code in the final commit.