Jake Worth

Jake Worth

All My Best Debugging Tips

Published: August 05, 2022 • Updated: November 29, 2023 9 min read

  • debugging

This is a list of all the best debugging tips I’ve picked up over the years.

Some of these might seem obvious, yet we forget them when it counts. Debugging is a skill. 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, things to do during debugging, and things to do after the bug is fixed.

A piece of advice that eclipses all others: be in the best mental condition possible before tackling a tricky bug. If you’re tired, hungry, have a headache, or are otherwise distracted, it’s going to be harder. It’s crucial that you can stay focused, hold stuff in your head, and be clear and decisive. I can’t count the number of times I’ve labored over a bug at the end of the day, gone home exhausted, then come back and fixed it almost instantly the next morning.

On to the tips!

Before Debugging

Prioritize Pair Programming

If you remember one tip in this list, it should be this: prioritize pair programming for bugfixes. You’ll fix the bug faster, learn (and teach) more, and strengthen your team all at once. 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, yet 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, Rubber Duck

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. I feel a little less alone even when talking to an object.

Say What You Know

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 aloud can surface some great questions, such as:

  • “We’re seeing a bug”… okay, so what is the expected behavior?
  • “on the homepage”… in which environment? Development, staging, production? What route?
  • “when it changes”… what actions can change the message count?

Start answering these, and you’re moving forward.

During Debugging

Stay Focused

Stay focused. Don’t get sidetracked. If something’s wrong with your text editor, or you need to update your browser, or there’s an error in your server log that precedes the bug, make a note and fix it later.

Sharpening the saw is great, and if a tweak improves your current situation, consider it. Just don’t let adjustments distract from the task at hand. There are endless things that could be improved, and irrelevant improvements can give a false sense of progress and create new problems.

This tip is crucial when you’re pair programming. With two people there’s double the temptation to embark on pointless, morale-crushing side quests (“this warning is distracting; let’s update npm”).

Reproduce the Bug

To fix a bug, you pretty much have to be able to reproduce it. On rare occasions, you can’t in a reasonable amount of time, but you must try.

Reproducing the bug proves that you understand it and lets you test your solution as many times as needed. Almost every time I’ve given up on reproducing the bug, my solution ended up not fixing it for the long term.

Make Reproducing the Bug Easy

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 ten times, so you can stop worrying about reproducing it and focus on observing it.

Is the bug only visible for a millisecond in the middle of a page reload? Throttle your network settings with dev tools so that it happens slowly enough for you to see.

Is the bug visible on one row in an index table? In a non-production database, delete all the other records so only the problematic row remains!

Perhaps the bug reporter spent a half-hour getting into the state where they found the bug; that doesn’t mean you have to. Automate the reproduction steps however you can so that you don’t waste any energy setting it up. Fake a time and date! Delete authentication logic! Change a giant conditional to true! If it works, do it.

When possible, automated tests are even better. Write a unit test that reproduces the issue, and start it in the ‘watch’ mode available on many test runners so that it re-runs whenever it or the files it covers change.

Make a Prediction

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 on the bug. That stand gives you something real— a predication that was either right or wrong— to consider.

Write Down Truths

When you learn something that you know is true, write it down.

Something that can kill a debugging session is backsliding. You’re zeroing in on the bug in the frontend, you lose focus, or bring in an observer who isn’t up to speed, and suddenly you’re asking “could this be a database issue?”

There are a lot of moving pieces in technology; 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 and don’t go backward.

It’s okay to revisit the assertion, but you shouldn’t go around it.

Print Values Well

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 information. Use labels so the signal stands out from the noise. Put your print statement in a condition that’s only true in your error case. And again, always make a prediction about what your printed message will be before you read it.

Debuggers are cool and useful, but printing is faster.

When I’m struggling, I find it tempting to log statements like "please work", "asdf", or "yah!", but it’s much better to write coherent messages. First, it will help you, because you’ll see "there's no way this should ever print" in the console and possibly have a course-altering realization. Second, assume someone is going to jump in and try to help you. Good log statements let them immediately start building on what you’ve learned.

During long debugging sessions, sometimes you need to abandon debugging statements that are no longer valuable or slowing you down. git checkout --patch is a way to drop changes from the command line without jumping through the many you’ve changed files.

Lastly, make the place you’re printing the information highly visible! If you’re in Chrome browser console, make it big, enlarge the fonts, and maybe use the console tab rather than the console window you can open in any tab. Or even put the data you’re curious about in an h1 at the top of the page. Make it impossible to miss the needle in the haystack.

Read the Stack Trace

Learn to read stack traces.

Very often when I solve a bug, I find that the exact issue, or something very suggestive of it, was printing 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.

Source Dive

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 document only some of the APIs that they’ve exposed. If you’re counting on an open-source library’s documentation to have the answer, you’re working with a fraction of the possible information available.

Get comfortable jumping into your dependency code, finding your bearings, exploring, and narrating what you see.

Avoid XY Problems

When you find yourself trying to solve an XY Problem, stop.

XP Problems are questions about your theoretical 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.

Implementing the wrong solution isn’t progress.

Use Marks

When you find a line of code that’s important, mark that line so you can return to it.

In Vim, m<letter> in Normal Mode marks your line. You can return there across files with '<letter>. I find this helpful when I’ve been moving through a lot of files and am losing focus. Return to the mark, jump start my memory, and keep going.

Use Version Control

Make version control your friend!

Get on a bugfixing Git branch immediately. When you get some code that works, even if it’s not perfect, commit it with a reasonable message such as “solves the bug, but we aren’t sure why” and push to your remote. If you have a solution you want to save, redirect it to a diff file that you can re-apply later, or just read and consider.

$ git diff > possible_solution.diff

Don’t redo work. Don’t throw away something that might be good enough. Ideally after working on a tough bug for a while, you’ll have a little pile of possible solutions to discuss. Now we’re discussing tradeoffs, rather than saying “it’s still broken.”

Don’t Be Afraid to Bisect

git bisect can identify when something stopped working. Learn to use it when necessary.

I don’t bisect unless I’m really stuck or I feel like the solution is going to be something sneaky and boring. 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. If it does, your issue doesn’t depend on the commented code. Comment out half the uncommented code and repeat.

Don’t Stay Stuck

Don’t stay stuck.

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.

After Debugging

Explain the Bug

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.

Test It

A bug in production wasn’t covered by a correct 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.

Conclusion

Here’s the full list of tips:

  1. Prioritize Pair Programming
  2. If You Can’t Pair, Rubber Duck
  3. Say What You Know
  4. Stay Focused
  5. Reproduce the Bug
  6. Make Reproducing the Bug Easy
  7. Make a Prediction
  8. Write Down Truths
  9. Print Values Well
  10. Read the Stack Trace
  11. Source Dive
  12. Avoid XY Problems
  13. Use Marks
  14. Use Version Control
  15. Don’t Be Afraid to Bisect
  16. Don’t Stay Stuck
  17. Explain the Bug
  18. Test It

Thanks to Jason Swett for sharing ‘All My Best Programming Tips’, which inspired this post.

What are your thoughts on debugging practices that make a difference? Let me know!


Join 100+ engineers who subscribe for advice, commentary, and technical deep-dives into the world of software.