Published: August 05, 2022 • 6 min read
This is a list of all the best debugging tips I’ve picked up over the years. It’s inspired by ‘All my best programming tips’ by Jason Swett. That is a great post and you should read it.
As Jason observed, some of these might seem obvious, yet we forget them when it counts. Debugging is hard. You have to bring every tool you have to the job.
I’ve divided this list into three sections: things to do before you start debugging, during debugging, and after the bug is fixed.
Prioritize pair programming for debugging. It’s the highest-bandwidth debugging technique I know.
Pairing can be tough. Sharing a dev environment remotely requires great tools. Resolving disagreements or experience gaps takes finesse. Constant conversation can tax. These are real, solvable problems.
When pairing works, you get combined years of experience, the diversity of opinions that each person brings, and even possibly, some fun.
If you can’t pair, use the Rubber Duck technique.
Humans think faster than they can talk. Slow your thinking down to the speed of speech, and bad ideas have nowhere to hide.
As an aside, Rubber Duck debugging often gives me a surprising morale boost. It’s like I’m talking to someone besides the cold computer.
I start debugging with a summary of what I know, as a sentence:
“We’re seeing a bug on the homepage where the user’s message count isn’t updating when it changes.”
Speaking a sentence like that can surface some great questions, such as:
These questions give you a path.
To fix a bug, you pretty much have to be able to reproduce it. On rare occasions, you can’t, but you must try.
Reproducing the bug proves that you understand it. Almost every time I’ve given up reproducing the bug, my solution ended up not fixing it for the long term.
Make the bug easy to reproduce, again and again.
Does the bug happen when a certain record is deleted? Write a little script that duplicates that record five times.
Is the bug only visible for a millisecond in the middle of a page reload? Throttle your network settings so that you can see it.
Maybe the bug reporter spent a half-hour getting into some state where they found the bug; that doesn’t mean you have to. Automate it however you can so that you don’t waste time. Fake time. Comment authentication out. If it works, do it.
Before testing anything, from your proposed solution or just an experiment, make a prediction. What do you think will happen?
Making a prediction forces you to take a stand. That stand gives you something tangible— a statement that was either correct or incorrect— to work with.
When you learn something that you know is true, write it down.
Something dangerous when debugging is backsliding. You’re zeroing in on the bug in the frontend, you lose focus or bring in a pair who isn’t up to speed, and suddenly you’re asking “maybe this is a database issue?”
To make progress, you have to be able to rule things out. If you’ve already figured out for sure that the issue is on the frontend, write down why that’s true.
You can revisit the assertion, but you shouldn’t go around it.
Learn to smartly print values so you can make and verify your predictions.
Rubyists call this Puts-Driven-Development because you’re printing with the
puts method. Learn how to print out the right thing, with labels so you can
find it, conditionally so it only prints when the bug conditions are met.
Debuggers are cool, but printing is faster.
During long debugging sessions, sometimes you need to abandon printed statements
that are no longer valuable.
git checkout --patch is a way to drop changes
from the command line without jumping through many files.
Learn to read stack traces.
Very often when I solve a bug, I find that the exact issue, or something highly suggestive, was described in a stack trace I had from the start.
Systems are complicated, and starting your debugging in or near the right part of the stack can save you hours of wandering.
Dependencies are just other people’s code that’s being used by your codebase. Learn to explore them.
Open-source documentation is great, but even the best projects correctly document only some of the APIs that they’ve exposed. If you’re counting on an open-source library’s documentation, you’re working with a fraction of the possible information available to you.
Get comfortable jumping into your dependency code, finding your bearings, and talking about what you see. It takes practice.
When you find yourself trying to solve an XY Problem, stop.
XP Problems are questions about your proposed solution, rather than questions about the actual problem. They sound strange when spoken aloud, like “how do I force React to update a props value, when the prop value hasn’t actually changed?” You’ll know you’re asking one when the person you ask responds with another question.
Successfully implementing the wrong solution isn’t progress.
When you find a line of code that’s important, mark that line so you can return to it easily.
m<letter> in Normal Mode marks your line. You can return there across
'<letter>. I find this helpful when I’ve been moving through a lot
of files and have lost focus. Return to the mark, see what started my
wandering, and refocus.
git bisect can identify a change where something stopped working. Learn to
use it when necessary.
I don’t usually bisect unless I’m really stuck and I feel like the solution is going to be something boring, like a missing semicolon. It’s a shortcut around learning how stuff breaks. Sometimes you need a shortcut.
Manual bisects are great too; comment out half of a function and see if the bug persists.
Timebox your solo debugging. Give yourself twenty minutes to an hour to struggle. When that ends, ask for help.
If you can’t get help and it’s an appropriate time to do so, go home. Let your subconscious work on the problem while you sleep.
If you want to cement what you’ve learned, explain the bug to someone else. Learn from it.
If the person is less experienced than you, congratulations; you just taught somebody something. If they’re more experienced, they might critique your explanation by pointing out holes in your reasoning. Now you understand it even more.
A bug in production probably wasn’t covered by an automated test. Solidify the bugfix by writing a test.
Much better than saying “I fixed the bug” is adding “and I wrote a test so it shouldn’t happen again.” Incremental improvements like that are what make a robust codebase.
What are your favorite debugging tips? What makes a real difference in your ability to quickly identify a bug?
✉️ Get better at programming by learning with me. Subscribe to Jake Worth's Newsletter for bi-weekly ideas, creations, and curated resources from across the world of programming. Join me today!
Blog of Jake Worth, software engineer in Maine.