Watching an experienced developer find a bug looks like intuition. It is not. It is a search executed in a particular order, and the order is learnable.
The loop
- Reproduce it. Reliably, on demand. If you cannot make it happen, you cannot confirm you fixed it.
- Narrow it. Cut the space in half, then in half again.
- Read what is actually there. Not what you meant to write.
- Form one hypothesis. Predict what you will see if it is true.
- Test that one thing. Change one variable.
- Fix, then verify the fix reproduces nothing.
Most wasted debugging time is spent skipping step 1 or violating step 5.
Reproduce first
"It sometimes fails" is not a reproduction. Keep cutting until you have the smallest input that fails every time.
Common hidden variables behind an intermittent bug: something time-dependent, something order-dependent, a cached value, a leftover record from a previous run, or two things racing.
Narrow by halving
The single most effective technique, and the most underused.
If the bug is somewhere in 200 lines, put one check in the middle. Correct at that point? The problem is after it. Wrong already? Before it. Seven checks locate a line in a codebase of a hundred thousand.
The same works across time. If it worked last month and fails now, bisect the commits: test the midpoint, discard half the history, repeat. Git even has a command that automates it.
The most expensive mistake
Changing several things at once. It feels efficient and it destroys your information: when the behaviour changes you no longer know which change did it, and if two changes had opposite effects you learn nothing at all.
One change. Observe. Then the next.
The second most expensive
Reading what you meant instead of what you wrote. Your eyes fill in the intended code. This is why saying the code aloud, line by line, works so unreasonably well, and why explaining the problem to someone often solves it before they answer.
Make the lesson usable
Debugging is controlled comparison. Reduce possible causes by finding a working case, changing one condition, and observing the earliest meaningful difference. Random edits create more variables and destroy evidence.
Run a narrowing experiment
- Write expected and actual behavior as two observable statements.
- Find the smallest input or action sequence that reproduces the problem.
- Inspect both sides of one boundary: input, function call, network response, database result, or rendered output.
- State one falsifiable hypothesis and predict what you will observe if it is wrong.
- Change or instrument one thing, record the result, and keep the evidence.
Check your understanding
01Why make a minimal reproduction?
It removes unrelated behavior and leaves fewer possible causes.
02What makes a hypothesis useful?
It predicts an observable result that can prove it wrong.
03Why avoid several fixes at once?
You lose the ability to identify which change affected the outcome.