Code Craft

Debugging as a Method

A systematic approach to finding faults: reproduce the problem, narrow it down, test one hypothesis at a time and verify the fix, instead of guessing.

On this page
  1. Why guessing fails
  2. Step 1: Describe the failure precisely
  3. Step 2: Reproduce it reliably
  4. Step 3: Shrink the problem
  5. Step 4: Divide and conquer
  6. Step 5: Form a hypothesis and test it
  7. Don’t forget the hardware
  8. Check your assumptions
  9. Step 6: Fix, verify and keep a record
  10. When you are stuck

Why guessing fails

When code or a device misbehaves, the natural reaction is to change something and see what happens. Sometimes that works. More often you change three things, the symptom shifts, and you no longer know which change did what. You can lose an afternoon this way and still not understand the fault.

A method does not make debugging effortless, but it keeps each step informative. The core idea is that a bug has a cause, and your job is to find evidence for it, not to try fixes until the symptom disappears.

Step 1: Describe the failure precisely

Write down what you expected and what actually happened. “It doesn’t work” is not a description. “The display shows 0 after about ten minutes, but is correct after a restart” is. Details such as when it started, what changed just before and whether it happens every time are often the best clues you have.

Step 2: Reproduce it reliably

A fault you can trigger on demand is half solved. If it happens only sometimes, look for what differs between good and bad runs: the input, the timing, the power source, the temperature, the length of time the device has been running. For intermittent faults, add logging so the next occurrence leaves a trail. Be aware that adding logging can itself change timing, which matters on small devices.

Step 3: Shrink the problem

Remove everything that is not needed to show the fault. A minimal example, with one sensor, one function and no extras, makes the cause far easier to see, and it is also what you would need if you asked someone else for help. Often the act of shrinking the problem reveals the culprit, because the bug vanishes when you remove a particular piece.

Step 4: Divide and conquer

If you do not know where the fault is, split the system in two and check which half holds it. This is a kind of bisection. For example:

  • Is the input data already wrong when it enters your function, or does your function produce the wrong result?
  • Does the sensor give sensible readings on its own, before your processing?
  • Did the last version work? With version control, you can step back through earlier commits and find the first one that fails, which narrows the search to a small change.

Each check halves the places the bug can hide, which is much faster than reading all the code.

Step 5: Form a hypothesis and test it

State a specific guess: “The value overflows because the counter is too small”, or “The sensor needs more time to settle after power-up.” Then design a test that could prove it wrong, such as printing the value or adding a delay. Change one thing at a time. If the result does not match your prediction, your mental model is wrong somewhere, and that is useful information.

Don’t forget the hardware

In maker projects the fault may not be in the code at all. Before assuming a software bug, check the basics:

  • Is the power supply delivering the voltage you expect, and does it hold up under load? Measuring with a multimeter gives you facts, and good multimeter readings are often the fastest way to rule out a whole category of problems.
  • Are the connections solid? Loose jumper wires are a classic cause of faults that appear and disappear, and careful breadboard wiring prevents many of them.
  • Are inputs floating when they should be pulled to a known level? Missing pull-up resistors are a common cause of random-looking readings.
  • Does the part’s datasheet allow what you are asking of it?

Swapping in a known-good part or running a simple test program is a clean way to separate hardware from software.

Check your assumptions

Most stubborn bugs sit where you were certain nothing could be wrong. List what you are assuming, such as “this variable is never negative” or “this function is only called once”, and verify each assumption rather than trusting it. Reading the error message carefully, line by line, is part of this. Messages are frequently more precise than they first appear.

Explaining the problem aloud, even to an object on your desk, often exposes a flawed assumption. This is a widely used trick and it works because it forces you to state the steps in order.

Step 6: Fix, verify and keep a record

When you think you have found the cause, fix that cause, not just the symptom. Then confirm the fix by repeating the original failing case, and check that nothing else broke. If you can, add an automated test that fails without the fix, so the same fault cannot return unnoticed.

Write a brief note about what the cause was and how you found it. Over time such notes build into a personal list of the faults you tend to meet, which makes the next hunt shorter.

When you are stuck

If hours pass with no progress, step away. A rested mind often spots what a tired one missed. When you ask others for help, share the minimal example, what you expected, what happened and what you have already tried. Remember that sharing code or logs publicly means removing any keys or passwords first.

Debugging is a skill that improves with a method, patience and honest notes, not with luck.

The complete guide to code craft

Software Habits Every Maker Should Learn
Read the guide

Keep exploring

Guides from other topics

All topics →