Tags: web-dev concept

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.