Chapter 13

Debugging

Follow the technical order. Binary search your assumptions.

On this page

Methodology for finding and fixing problems.


The Nature of Bugs

Something isn't working. The code is doing something you didn't expect. There's a gap between what should happen and what does happen.

That gap is the bug. Your job is to find it, understand it, and close it.

Most developers debug by intuition, change things until it works. This is slow, unreliable, and doesn't teach you anything. There's a better way.

I learned debugging on the flight line before I ever touched a codebase. Aircraft maintenance is troubleshooting with higher stakes: hydraulic systems, electrical faults, mechanical failures. You don't guess. You follow the technical order. You isolate the system. You test components. You verify the fix before the aircraft goes back on the line.

The methodology is the same whether you're tracing hydraulic pressure or data flow. Gather information. Form hypotheses. Test. Isolate. Fix. Verify. Debugging is the scientific method applied to code.


The Methodology

  1. Reproduce it. If you can't reproduce it, you can't fix it with confidence. The strongest reproduction is a failing test: it proves the bug exists and announces when it's gone.
  2. Isolate it. Narrow down where the problem is. Cut the system in half; is the bug in this half or that half? Repeat. When it used to work, git bisect runs the same search through time.
  3. Understand it. Know why it's happening before you fix it. The obvious fix applied to an ununderstood bug either doesn't fix it, breaks something else, or lets it come back.
  4. Fix it. Make the smallest change that addresses the root cause. Not the symptom: if a null check would prevent the crash, first ask why the value is null. Don't refactor while fixing.
  5. Verify it. The failing test passes, the rest still pass, the original reproduction no longer works, and production metrics agree.
  6. Learn from it. How did it get introduced? Why didn't tests catch it? Is the same pattern elsewhere? Add the test, add the logging, tell the team.

Each step matters. Skip one and you're guessing.

One honest limit on step 1: production sometimes leaves you only logs and incomplete evidence, and users can't wait for a clean reproduction. Then you mitigate from what you have, add the instrumentation you wish you'd had, and wait for the bug to show itself again. That's legitimate work. Just call it mitigation, not a fix, until the reproduction confirms it.


Binary Search Your Assumptions

When you're stuck, you're holding an incorrect assumption. One of your beliefs about the system is wrong. Find it.

  1. List your assumptions. What do you believe is true? Write them down.
  2. Verify each one. Don't assume; prove. Check with logs, debugger, tests.
  3. Find the false belief. The bug lives behind the wrong assumption.

Common false assumptions:

  • "This variable has the value I expect"
  • "This function is being called"
  • "This code path is being executed"
  • "The data is in the format I expect"
  • "The external service is behaving correctly"
  • "The cache is being invalidated"
  • "The configuration is what I think it is"

Any of these could be wrong. Verify.


The Mindset

Read the error message. All of it. Slowly. Then read it again. Developers see "error" and start guessing; the message often says exactly what's wrong and where.

Change one thing at a time. Make three changes and the bug disappears, and you don't know which one fixed it — or whether you just hid it. Test after each change.

When you're stuck, step away or explain it. Fresh eyes see what tired eyes miss, including your own after a walk. Explaining the problem out loud often finishes the debugging before the listener says a word.

Check your code first. Before you blame the framework, the library, the compiler, or the hardware, check your code again. The tools have been tested by millions of users. Your code has been tested by you. The bug is in the last place you'd look, so look there first.


The 2 AM Layer

This chapter is the read-once layer. When something is actually on fire, use Quick Reference: Debugging: the same methodology as checkboxes, built for the moment you can't read prose. The common mechanical culprits (bounds, nulls, races, stale caches, environment drift) live in every debugging tutorial ever written; what none of them can list for you is your own false assumption, which is why the binary search above is the part worth training.


Reproduce. Isolate. Understand. Fix. Verify. Learn. This is the methodology. Everything else is guessing.