Debugging Techniques
Date: 2026-08-17
Finding out why something does what it does rather than what you expected. The skill is almost entirely in narrowing the search space systematically — most time lost to debugging goes on guessing at causes instead of halving the possibilities.
Debugging is locating the cause of a discrepancy between expected and actual behaviour.
The whole technique is search-space reduction. Everything below is a way of eliminating half the possibilities.
The sequence
1 REPRODUCE reliably
← if you can't, you can't verify
a fix either
2 MINIMISE
strip away until the smallest
thing that still fails
3 BISECT
halve the space: which commit,
which file, which line, which
input
4 HYPOTHESISE
one falsifiable guess
5 TEST IT
one change at a time
6 VERIFY the fix, and understand WHY
it works
Step 6 is the one skipped under pressure. A fix you don’t understand is frequently a coincidence, and it will come back — Error Handling Strategies.
Reproduction is most of the work
CAN'T REPRODUCE → gather conditions
browser, device,
account state,
sequence, timing,
network
— Session Replay
INTERMITTENT → suspect a race or
shared state first
— Race Conditions
ONLY IN PRODUCTION → what differs?
data, scale, config,
third parties,
real network
See: Session Replay · Race Conditions
“Works locally” is information, not a dead end. The list of things that differ between local and production is short and enumerable, and the bug is in it.
Bisection, everywhere
The technique that generalises furthest:
IN HISTORY git bisect — which commit
— Git Bisect
IN CODE comment out half; still
broken?
IN DATA half the rows; still
broken?
IN CONFIG default config; still
broken?
IN DEPENDENCIES remove them one at a time
See: Git Bisect
Ten halvings search a thousand possibilities. Reasoning about which of a thousand is at fault takes far longer and is frequently wrong — Time and Space Complexity.
Logging discipline
BAD GOOD
console.log(x) console.log(
console.log('here') 'calculateTotal',
console.log('here2') { basketId, items:
items.length,
subtotal })
Log the identifier and the values, not a marker. A log line saying “here” tells you the code ran; you already knew that.
- Structured logs — objects, not concatenated strings — so they can be filtered and searched
- Include a correlation id so one request’s lines can be gathered across services
- Remove them, or promote them to real logging. A codebase littered with debug logs is one where nobody can find the useful ones — Linting
Browser tools worth knowing properly
conditional breakpoint right-click a
line → break only
when id === 42
logpoint logs without
modifying source
break on DOM change "why does this
element change?"
XHR/fetch breakpoint break when a URL
is requested
pause on exception catches the throw
in its own context
Network → throttling the bug that only
appears on slow
connections
— Latency and Bandwidth
Performance recording for jank
— The Rendering Pipeline
See: Latency and Bandwidth · The Rendering Pipeline
Conditional breakpoints and logpoints replace most console.log editing, and logpoints have the advantage of not requiring a rebuild.
Read the error properly
TypeError: Cannot read properties of
undefined (reading 'name')
at renderCustomer (Customer.tsx:42)
That names the file, the line, and the exact property. A surprising amount of debugging time goes on skimming past a message that already said what was wrong.
Read the whole stack, from the bottom up — the top frame is where it threw, and the frame below is usually where the wrong value came from — Source Maps.
Rubber-ducking
Explaining the problem aloud, in full, to someone who isn’t listening.
It works because articulating forces you to state assumptions, and the wrong one is usually exposed in the act of saying it. The solution frequently arrives mid-sentence, before the other person has responded.
Write it out as a bug report if there’s nobody to talk to — the effect is the same and you end up with a report.
When to stop
STUCK FOR AN HOUR?
□ take a break — genuinely effective
□ explain it to someone
□ re-read the actual error
□ question an assumption you've
been treating as fact
□ bisect from a known-good state
□ check the boring causes: cache,
stale build, wrong branch, wrong
environment
The boring causes are boring and extremely common. A meaningful share of “impossible” bugs are a stale build, a cached asset, or being on the wrong branch — and checking takes thirty seconds.