Debugging is a reading skill
Most bugs are found by reading carefully, not by running more code.
When something breaks, my first instinct is still to run it again. Add a print. Add five more. Change a line, see what happens. It feels like progress because the terminal keeps spitting out output, and output feels like information.
Most of the time it isn’t. I’m making noise around a question I haven’t even asked yet.
The bugs I’ve actually fixed, I fixed by reading. Not skimming for the line that looks suspicious. Reading slowly, one line at a time, asking what each line assumes.
Walk backwards
Every bug is a gap between what I think the code does and what it actually does. Running it shows me the result. Reading it shows me where my assumption broke, and that’s the part I carry to the next bug.
So I start where it crashed and walk backwards, in plain sentences. This function gets a document. It reads a field from the parent. The parent is fetched by name. Where does the name come from? Four steps back there’s a line that assumes the parent always exists, and one code path where it doesn’t.
Prints would have shown me a None. I’d have added a guard, and the bug would have moved somewhere else.
It feels slow, and that’s fine
Twenty minutes into reading, I have nothing to show. No commit, no output. Poking at it would give me a new change every two minutes, none of them right.
But if I can’t explain why the code did what it did, I can’t say my change fixes it. And I definitely can’t say it doesn’t break the other four places that call the same function. A fix I can’t explain is a coincidence I committed.
What I read
Not everything. Picking what to read is most of the skill.
The call site tells me what the author expected. The tests tell me what they were worried about, and a missing test says more than any comment. git blame tells me if a weird line is there on purpose or left over from something.
Bugs also love boundaries: where a value moves from one layer to another, where a type gets loosened, where someone wrote a helper to hide something awkward. The line that crashed is rarely the line that’s wrong.
How I know I’m reading and not guessing
Reading gives me a prediction. “If I’m right, this input should give this exact wrong output.” Then I run it once, to check.
Guessing works the other way round. Change something, hope, run it to find out. Whenever I catch myself running code to learn what it does instead of to confirm what I already think, I stop and go back to reading.
That’s also why debugging gets faster over the years while my typing speed stays the same. I’m not getting quicker at the loop. I’m going around it fewer times.