P-1.5 How Programs Fail, and How to Read That

Standard debugging method — written September 2026

What this is and why it exists

Debugging is taught by osmosis almost everywhere, which is why so many people do it by changing things until the symptom moves. That is not a method. It is a way of arriving at code that appears to work for reasons nobody can state.

Debugging is an ordinary skill with an ordinary method. Decide which of three kinds of failure you have. Read what the machine actually said. Form one hypothesis. Test it by changing one thing.

There is also one habit to unlearn, and it is nearly universal: reading the first line of an error and stopping. The line that names your own code is usually further down.

The vocabulary

  • Compile error — it would not build. Nothing ran.
  • Crash — it started and stopped part way, with a complaint.
  • Wrong answer — it ran to completion and produced something incorrect. Nothing announces this one.
  • Stack trace — the chain of calls, from the point of failure back out to where it started.
  • Frame — one call in that chain.
  • Reproduction — the smallest set of steps that makes the fault happen on demand.
  • Hypothesis — a statement about the cause, specific enough to be wrong.

The mental model

The three kinds of failure have completely different causes and completely different methods. Naming which one you have is the first move, not an afterthought. A build failure is a conversation with a tool about your text. A crash is a conversation about state at one moment. A wrong answer is the dangerous one, because nothing announces it — the program finishes, returns zero, and hands somebody an incorrect number.

Reading errors properly is the next skill. A stack trace reads from the point of failure outward, through everything that called it. The frame that matters is usually the deepest one inside code you actually wrote. Skipping to the top line is how people conclude that a widely used library is broken, which it almost never is.

Then: reproduce before you fix. A fault you cannot make happen on demand cannot be confirmed fixed — you will change something, not see the symptom, and call it done. And make the reproduction small. Cut the case down until nothing more can be removed. This does double duty, because the act of cutting it down usually identifies the cause on its own, before any fix is attempted.

Now the habit that separates method from flailing: one hypothesis at a time, one change to test it. Change three things and watch the symptom disappear, and you do not know which mattered. You may well have added a second defect that the first one was hiding. Write down what you expect to happen before each run, then compare. When the result surprises you, that surprise is the most valuable thing you will get all day. It means one of your beliefs is wrong, and you have located it.

On tools: printing values is fast, honest and always available, and it works in places a debugger cannot go. A debugger lets you stop the program and inspect everything at once, which is worth a great deal when the state is large. Both are worth having. Knowing which suits the fault in front of you is what saves the hours.

What you should now be able to explain or do

Name the three kinds of failure and say why the third is the dangerous one. Read a stack trace to the frame that is yours rather than stopping at the top. Reproduce a fault on demand and cut the reproduction down until nothing can be removed. State one hypothesis, change one thing, and write down what you expect before you run. Say when printing suits a fault better than a debugger, and when it does not.

Check yourself

The three have different causes and different methods. Applying a crash method to a wrong answer wastes the whole session.

The wrong answer. The program completes and reports success, so nothing draws attention to it and it can travel a long way before anyone notices.

Usually the deepest frame inside code you wrote. Reading only the top line is what leads people to blame the library.

A fault you cannot trigger cannot be confirmed fixed, and shrinking the case usually reveals the cause before you attempt a fix.

If the symptom goes away you do not know which change mattered, and you may have added a defect that the original was masking.

Go deeper

We haven't checked most of these for screen reader use yet.

Back to How Programs Fail, and How to Read That: work through the checklist