Skip to content
SparkTime, home

Guide · Technology

How to Debug Any Bug: A Calm, Repeatable Process

Random changes and hopeful refreshes waste hours. This is a step-by-step debugging process that works in any language: reproduce, narrow down, form a hypothesis, test it, then fix the cause.

7 min readIntermediate

For

Students and developers in their first few years who lose too much time to bugs.

Level

Intermediate

Before you start

Basic programming in any language. Examples use JavaScript and Python, but the process applies everywhere.

On this page

Key takeaways

  • Read the whole error message, especially the first line and the part of the stack trace that points to your own code.
  • If you can't reproduce a bug reliably, you can't confirm you've fixed it. Reproduce first.
  • Narrow the problem down by halving the search space, not by guessing.
  • Change one thing at a time, and predict what will happen before you run it.
  • Fix the cause, not the symptom, then add a test so the bug can't come back silently.

Every developer knows this afternoon. The feature worked yesterday and doesn't today. You change a line, refresh, and it still doesn't work. You change another line. You add a console.log, then six more. Two hours later you've edited eleven files, broken something else, and you're no longer sure what the original problem was.

Debugging doesn't need talent or luck. It needs a process, and a calm one beats a clever one almost every time.

Here's a process that works in any language, on any stack, for bugs big and small.

Step 1: Read the error message. The whole thing.

#

It sounds obvious, but most people glance at the red text, panic slightly, and start changing code.

Slow down. Most error messages tell you three things:

  1. What went wrong (the error type)
  2. Where it happened (file and line number)
  3. How it got there (the stack trace)

Take this Python error:

Code
Traceback (most recent call last):  File "app.py", line 42, in <module>    total = calculate_total(order)  File "billing.py", line 17, in calculate_total    return sum(item["price"] * item["qty"] for item in order["items"])  File "billing.py", line 17, in <genexpr>    return sum(item["price"] * item["qty"] for item in order["items"])KeyError: 'qty'

The last line says what happened: one of the items doesn't have a "qty" key. The trace points to billing.py, line 17. So the question isn't "why is billing broken?" It's "which item has no qty, and why?". That's a much smaller problem.

A few tips:

  • In Python, the most useful line is usually the last one. In many other languages, it's the first line of the error.
  • In a long stack trace, look for the first frame from your code rather than from a library.
  • Copy the exact error text. Searching for it, with your own variable names removed, often finds the answer.

Step 2: Reproduce it reliably

#

If you can't make the bug happen on demand, you can't tell whether a change fixed it or you just got lucky.

Find the exact steps that trigger it:

  • Which input, which user, which data?
  • Which environment: local, staging, production? Which browser or device?
  • Does it happen every time, or only sometimes?

Then make the reproduction smaller. If the bug appears during a 12-step checkout flow, can you trigger it with a single API call? A unit test? Three lines in a console? The smaller the reproduction, the faster each attempt, and the fewer places the bug can hide.

When it only happens "sometimes"

#

Intermittent bugs usually come from something that varies between runs:

  • Timing: race conditions, async code finishing in a different order, timeouts
  • Data: a particular record, an empty list, a null value, a special character in a name
  • Environment: a different config, a missing environment variable, a cache, the time zone
  • State: something left behind by a previous request or test

Write down every detail of the times it failed, and look for what they have in common.

Step 3: Narrow down where it happens

#

Now find the smallest piece of code where things go from right to wrong. Don't guess. Halve the search space.

Binary search with logs

#

If data goes in correct at step A and comes out wrong at step F, check in the middle at step C:

JavaScript
console.log("after parseOrder:", JSON.stringify(order));
  • If it's already wrong at C, the bug is between A and C.
  • If it's still right at C, the bug is between C and F.

Repeat. With ten steps, you'll find the faulty one in three or four checks instead of ten.

Use a debugger

#

print and console.log are fine, but a real debugger is often faster. Set a breakpoint, pause execution, and inspect every variable at that moment. Step through line by line and watch where the values go wrong.

Every major editor and browser has one: VS Code, the Chrome DevTools Sources tab, IntelliJ, PyCharm. Learning to use one well is one of the best investments a developer can make.

Binary search through history

#

If it worked last week and doesn't now, the bug arrived in a commit. git bisect does a binary search through your commit history for you:

Shell
git bisect startgit bisect bad              # the current version is brokengit bisect good a1b2c3d     # this older commit worked# Git checks out a commit in the middle. Test it, then run:git bisect good             # or: git bisect bad# ...repeat until Git names the first bad commitgit bisect reset

Among a hundred commits, that finds the culprit in about seven steps.

Step 4: Form a hypothesis, then test it

#

Once you know roughly where the problem is, make a specific guess:

"I think qty is missing because orders created from the mobile app send quantity instead of qty."

Then predict what you'll see if it's true: "If I log an order from the mobile app, it will have quantity and no qty."

Now check. Either:

  • You're right. You understand the bug. Go to step 5.
  • You're wrong. Good, you've ruled something out. Form the next hypothesis.

The key discipline: change one thing at a time. If you change three things and the bug disappears, you don't know which change fixed it, or whether one of the others caused a new problem.

When you're stuck: explain it out loud

#

Explain the problem, line by line, to someone, or to a rubber duck on your desk (this really is called rubber duck debugging). Describing what the code is supposed to do, step by step, often makes you notice where it doesn't. It works surprisingly often, usually halfway through a sentence.

Step 5: Fix the cause, not the symptom

#

Back to the KeyError. A quick "fix" would be:

Python
item.get("qty", 1)

The error disappears. But if the real cause is that mobile orders send quantity, every mobile order now silently gets a quantity of 1. You've swapped a loud bug for a quiet one, and quiet bugs are much worse.

The real fix might be to:

  • Normalise the field name where orders come in, or
  • Fix the mobile app to send the right field, and
  • Validate incoming orders so a missing quantity is rejected with a clear error

Before you fix anything, ask: why did this happen, and where else could it happen? If one endpoint mishandled the field, others might too.

Step 6: Prove it, and prevent it

#
  1. Run your original reproduction. The bug should be gone.
  2. Write a test that would have caught it. Ideally, write the test before the fix, watch it fail, then watch it pass.
  3. Run the wider test suite to make sure you haven't broken anything else.
  4. Remove your debug logs, or turn the useful ones into proper logging.
  5. Write a clear commit message saying what was wrong and why, for whoever reads it later. That might be you.

The test matters most. Without it, the same bug can come back months later after someone else's change, and nobody notices until a customer does.

Common culprits worth checking first

#

Experience is mostly a list of places bugs like to hide. A few that show up again and again:

  • Null / undefined / None values you didn't expect
  • Off-by-one errors in loops, slices and pagination
  • Type mix-ups: "10" + 5 in JavaScript, integer division, strings compared with numbers
  • Time zones and dates: especially IST vs UTC on servers, and month indexes starting at 0 in JavaScript
  • Async timing: using a value before a promise resolves, or a missing await
  • Caching: the browser, CDN, build tool or app serving an old version
  • Environment differences: a variable set locally but missing in production
  • Copy-paste errors: duplicated code where only one copy was updated

Using AI for debugging, the right way

#

AI assistants can be excellent debugging partners when you give them good material:

  • The full error message and stack trace
  • The relevant code, not the whole project
  • What you expected, what actually happened, and what you've already ruled out

Ask it to explain the likely cause before you ask for a fix. Then verify its explanation against your own reproduction. If you paste its fix without understanding the cause, you're back to random changes, only faster.

And never paste production secrets, customer data or proprietary code into a tool your company hasn't approved.

Frequently asked questions

#

Should I use print statements or a debugger?

#

Both have a place. Print or log statements are quick and work everywhere, including production logs. A debugger is better for exploring complex state step by step. Learn both, and reach for the debugger more often than feels natural at first.

How long should I try before asking for help?

#

A common rule of thumb is to spend 30–60 minutes making real, methodical progress, then ask. When you do, share what you've tried and ruled out. It saves your colleague time and shows you've done the groundwork.

Why does my code work locally but not in production?

#

Usually because of environment differences: configuration and environment variables, data that doesn't exist locally, different versions of dependencies, file paths, permissions, time zones or network access. Compare the two environments carefully, and check production logs before guessing.

What's the best way to debug code I didn't write?

#

Reproduce the bug first, then trace the path of the data from input to the wrong output, using logs or a debugger. You don't need to understand the whole codebase, only the path the bug travels.

How do I get better at debugging?

#

Debug deliberately. After each bug, spend two minutes noting what the cause was and what would have found it faster. Over months, that list becomes the instinct people call experience.

  • Guide · Technology

    How to Learn Programming When AI Can Already Write Code

  • Guide · Technology

    Debugging: Errors, Stack Traces, the Debugger and Logging

  • Guide · Career

    A LinkedIn Profile That Gets Recruiter Messages