The Six Stages of Debugging (and How to Escape Printf Hell)
Every developer knows this journey.
- A bug is reported.
- You’re confident it’s impossible.
- A few hours later you’re staring at the code wondering how it ever worked at all.
Here are the six stages of debugging - which we’re all going to go through at some point in our lives:
- That can’t happen
- That doesn’t happen on my machine
- That shouldn’t happen
- Why does that happen?
- Oh, I see
- How did that ever work?
Stage 1: That can’t happen
The first stage tends to be denial.
We get a bug report in and our response is immediate: “That can’t happen.” What’s being reported is simply impossible — our software does not behave in that way.
Stage 2: That doesn’t happen on my machine
The second stage is more denial, just slightly more polite.
There’s a grudging acceptance that the user may be experiencing some kind of problem… but we can’t recreate it.
It’s the classic: “Works on my machine.”
Stage 3: That shouldn’t happen
Now things are starting to get interesting.
We’ve managed to recreate the bug, but we know in our heart that it should not be happening. The code is not supposed to behave like this.
There is clearly something wrong with the universe.
Stage 4: Why does that happen?
This is where we’re finally getting into the fun part of debugging.
We’ve accepted reality, and now we’re asking the right question: What is our code actually doing?
This is the most important stage — and also the one where we can easily get stuck.
Stage 5: Oh, I see
If you’re lucky, your debugging leads you to the solution.
Suddenly the problem is obvious. The code is wrong — and now you know exactly why.
This is the moment every developer is chasing.
Stage 6: How did that ever work?
The final stage is an existential crisis.
The code is definitely broken. You’re left scratching your head, wondering how this thing ever worked in the first place.
The real challenge
The real trick is getting from stage 4 — “Why does that happen?” — to stage 5 — “Oh, I see” — as quickly as possible.
On this blog and YouTube channel, we’re often dealing with embedded devices - particularly the ESP32 range of MCUs.
What are our debugging options?
Printf debugging
For anyone familiar with the Arduino framework, these two functions will be very familiar:
Serial.printf("...");
Serial.println("...");
We litter our code with lines such as:
Serial.println("Here 1");
Serial.println("Here 2");
And eventually end up with things like:
Serial.println("Should not be here");
Serial.println("Really should not be here");
Serial.println("Why the **** are we here!?");
Printf debugging works — up to a point.
It’s quick. It’s easy. And it’s often the only tool we reach for.
But it’s also slow, messy, and gives us very limited visibility into what’s actually going on.
Wiggling GPIO pins
Another option with embedded systems is outputting values on GPIO pins.
We can hook up some LEDs to make this more visible, or if we’re very lucky, connect a logic analyser and really dig into what’s happening. These tools are powerful and can do all sorts of clever things, like decoding protocols.
But even then, we’re still inferring behaviour rather than observing it directly.
There is another way
If you’re debugging an ESP32 project, you might think these two options are all you have:
- Printf debugging
- Wiggling LEDs
But there is another option.
Recent ESP32 modules have built-in USB peripherals — and more interestingly, built-in JTAG debugging support.
That means you can add breakpoints to your code and simply hit “Start Debugging” in the Arduino IDE.
It really can be that simple.
With a real debugger, you can:
- Set breakpoints
- Use conditional breakpoints
- Inspect variables
- Modify values while the code is running
It’s just like debugging code on your desktop — but on an embedded device.
See it in action
Here’s a short video showing what’s possible when you use the ESP32’s built-in USB debugging support, including breakpoints, variable watches, and conditional logic.
If you’re tired of being stuck in stage 4, this might be the quickest route to stage 5.