Jahed Ahmed

When all else fails, add breakpoints.

There's nothing more tedious than manually stepping through lines of code. Being jumped around from one place to another. Reading stack traces to find where you are. Variable lists to figure out the current state of things and how it all went wrong. Breakpoints should never be the first tool to reach for. It's the tool best avoided. So, how do you avoid them?

Understand the Flow

More often than not, bugs are visible when you read the code and understand the flow. Usually, something's clearly not right for the scenario you're dealing with. Too many variables, odd mutations, some out of sync parallelisation.

Attempting to use breakpoints before understanding the flow will leave you lost. Like opening a book at a random page. Sure you can make out some details, but you're probably making the wrong assumptions.

How you go about understanding is up to you. Leave some comments, write it out in plain text, draw some diagrams.

Add Tests

Now that you understand the flow, somewhat, at least, better than before, add a test. If you're already coming from a test, then you're probably fine. Though consider if the test is too high-level. Do you need a smaller scope?

Maybe the current test goes through too many discrete steps. Maybe it's testing too many modules. Maybe it expects too much.

Whatever you do, make sure there's a test. Otherwise you'll waste time manually repeating the scenario and probably making mistakes. On every run, you want the exact same setup. And once the bug is fixed, it'll keep it fixed. That's what tests are for.


Now that you've made a test, it's safe to refactor. This may seem counter-intuitive. There's a bug somewhere, and you're moving stuff around. Wouldn't that make it harder to debug? No. There's a bug somewhere and you need to cut down some trees so that it's easier to find.

The test is your bug net. Use it as a guide. Turn off unrelated code paths. Make things easier to read. Poke around and get a deeper understanding of what the code aims to achieve. Improve it so that there's less need for breakpoints and less chance of introducing more bugs in the future.

Improve Observability

At this point, add some logging. Every test run will give you a state dump. You don't need to follow breadcrumbs one at a time. If you find yourself doing this often, maybe those logging should be permanent. It can help others in the future.

When all else fails, add breakpoints.

Thanks for reading.