Key points
- Debugging usually starts from a failure someone can observe and ends when the same steps pass.
- Testing shows that something is wrong, and debugging finds the cause and removes it.
- A coding agent's report that a bug is fixed is a claim until the failing steps pass.
What is debugging?
Debugging is the process of finding and fixing the bug behind a software failure, then confirming that the failure is gone. It usually starts from a failure that someone can observe, e.g. a wrong order total. It ends when the steps that failed before now pass.
A defect, often called a bug, is a flaw in the code. A failure is what a user or a test observes when that code runs. Debugging works backward from the failure to the defect, which often sits far from the place where the failure shows. A compiler or interpreter reports a syntax error with the line where it found the error, so debugging usually targets runtime errors and logic errors.
Reproducing the failure before changing any code gives the team a check that later confirms the fix. A change made without that check has not been shown to fix anything. The steps are the same whether a person or a coding agent wrote the code.
How does debugging work?
A debugging session usually moves through these steps:
- A person or a test reports a failure, with the steps that led to it and the observed and expected results.
- The developer reproduces the failure by running the same steps on the same version of the software.
- The developer narrows down where the failure starts, e.g. by adding log lines around the suspect code.
- The developer traces the failure to its root cause, the first wrong value or decision that leads to it.
- The developer changes the code to remove that cause, not only the symptom.
- The developer reruns the steps from step 2 and checks that the failure no longer happens.
- The developer runs the wider suite, a step called regression testing, to check that the fix broke nothing that worked before.
Tracing a failure past its symptoms to its cause is called root cause analysis. If the rerun in step 6 still fails, the loop goes back to step 3. If step 2 does not reproduce the failure, the difference usually lies in the conditions, e.g. the data the software ran on. The developer then changes one condition at a time until the failure appears.
What are the types of debugging techniques?
Most debugging combines a few techniques. Each one answers a different question about the failure.
Print and log debugging
Print debugging adds output statements that show a value at a chosen point, e.g. the number of items in the cart. Logging does the same in a running system and keeps a record after the failure. Both are cheap to add, but they show only the values someone chose to print.
Interactive debugging
A debugger is a tool that pauses a running program at a breakpoint. A person can then inspect variables and step through the code one line at a time. Many languages ship one with their standard tools, e.g. pdb in the Python standard library.
Stack trace analysis
A stack trace lists the chain of function calls active when an error happened, from the failing line back through the calls that led to it. It shows where the error surfaced, which is often later than the point where the wrong value was created. Working backward from that line to find where the value came from is often called backtracking.
Bisection
Git bisect runs a binary search through a repository's history to find the first commit where a behavior changed. A developer marks one good commit and one bad commit, and each commit tested between them roughly halves the range left to check. With git bisect run, a script tests each commit that bisect checks out and reports the result through its exit code. Git treats 0 as good, 125 as a commit that cannot be tested, and other codes from 1 to 127 as bad. Any other code stops the bisect.
Input reduction
Input reduction removes parts of a failing input, or steps from a failing scenario, as long as the failure still happens. The goal is a minimal reproducible example, the smallest complete code, input, and setup that still shows the bug. Delta debugging is an algorithm that automates this search.
Explaining the code aloud
In rubber duck debugging, a developer explains the code line by line to a listener, which can be a rubber duck on the desk. Saying what each line should do often exposes the line where the code and the explanation disagree.
What is an example of debugging?
Here is an illustrative example. Acme Co. sells furniture online. A developer at Acme asked a coding agent to "Let customers edit their delivery address during checkout." After the change merged, a customer reported an empty order summary.
The developer reproduces the failure first. They add 2 items to the cart, start checkout, change the delivery address to "12 Elm Street," and continue. The address saved, but the cart emptied.
To narrow it down, the developer logs the checkout record on each side of the address update:
checkout A-1042 before setAddress items=2 total=$240.00
checkout A-1042 after setAddress items=0 total=$0.00
The cart is intact before the update and empty after it, so the cause is inside the update. The address handler replaces the whole checkout record with one that holds only the address. The fix copies the existing record and changes only the address:
- checkout = { address: newAddress };
+ checkout = { ...checkout, address: newAddress };
The developer repeats the steps from the reproduction. The cart still holds 2 items, and the total is still $240.00. A test that changes the address and checks the cart joins the suite, so the suite fails if the same bug returns.
This example is simplified. A real bug often takes several rounds of narrowing before the cause is this clear.
What changes when a coding agent writes the code?
A coding agent can run much of the debugging loop itself, e.g. rerunning a failing command after each change. What changes is where the failure comes from and who reports the fix.
In Stack Overflow's 2025 survey, the most common problem developers reported with AI tools was answers that are almost right but still wrong. Of the developers who answered that question, 66% had run into it. Code that is almost right often passes the common cases and fails in narrow ones, so its failures take exact steps to reproduce.
A developer debugging agent-written code often has not read all of it or does not know why each line is there. The agent's session history shows what it was asked and what it reported. The diff shows which files changed, so narrowing down can start there.
When an agent fixes its own bug, its output can say "done" before anything reran the failing steps, which is a false completion claim. In METR's 2025 randomized trial, experienced open-source developers took 19% longer with AI tools, although they believed the tools had made them about 20% faster. METR has since said that this result is out of date. A practical adjustment is to accept an agent's fix only after the failing steps pass on a fresh run, or several runs for an intermittent failure.
What are the limits of debugging?
Debugging has five common limits:
- It needs something to start from. Debugging explains a failure that someone observed or removes a defect that a review found. It does not look for defects that nothing has exposed yet.
- Some failures do not reproduce on demand. An intermittent failure can depend on timing, e.g. a race condition between two requests. A debugger or extra logging can change that timing and hide the bug.
- A fix without a reproduction cannot be confirmed directly. A team can still fix a clear cause found in a stack trace or a log. The team then adds logging and watches whether the failure returns.
- A passing rerun covers one failure. It shows that the failure it repeated is gone, not that the software has no other bugs. An intermittent failure needs several reruns before a pass counts as evidence.
- The time it takes is hard to predict. The cause is unknown until it is found, so nobody can say in advance how many rounds of narrowing a bug will take.
How is debugging different from testing?
Software testing runs or examines software to find differences between what it does and what it should do. Debugging starts from one of those differences, finds its cause, and removes it. A failing test shows that something is wrong but does not explain why.
The two overlap at both ends, because a failing test often starts a debugging session, and a rerun of that test confirms the fix. Troubleshooting is a wider term for finding the cause of a problem in a running system, e.g. a wrong setting. It can end without any change to the code.
What does debugging cover?
These articles each cover one part of the debugging loop:
- Reproduction steps are the exact actions, inputs, and conditions that make a bug happen again.
- A bug report that a coding agent can fix describes one failure in enough detail to rerun it.
- Flaky tests pass and fail on the same code, so a single result from one cannot show a bug or a fix.
- Pass on retry marks either a flaky test or an intermittent bug, and controlled reruns with kept evidence tell the two apart.
- Race conditions make a result depend on the timing or order of events that run at the same time.
- Root cause analysis traces a failure past its symptoms to the cause, so the fix removes it.
- Agents break working features when their changes reach code that other features depend on.
- Bug fix verification repeats the steps that exposed a bug on the fixed code, then checks that nearby behavior still works.
How do you get a failure an agent can act on?
Give the agent a failure it can run again, with the exact steps, the observed and expected results, and the raw output, e.g. the stack trace. Keep any guess about the cause apart from those facts and label it as a guess. A wrong cause stated as fact can lead the fix to the wrong code.
RunStory runs your software, sends reproducible failures to your coding agent, and verifies the fix. Your coding agent receives the actions RunStory took and evidence of the unexpected result. After your agent makes the change, RunStory repeats the failing workflow to check that the problem is resolved. It is in private alpha for CLIs and web apps.
FAQs
Should every defect be reproduced before it is fixed?
A defect that caused a failure should be reproduced before it is fixed whenever the team can do it, because the same steps later confirm the fix. A defect found in a review has no failure to reproduce. When a failure will not reproduce, a fix to a clear cause cannot be confirmed directly.
How do you debug a bug you cannot reproduce?
To debug a bug you cannot reproduce, start from the evidence it left, e.g. a stack trace. Compare the conditions where it failed with your own and change one condition at a time. Add logging near the suspect code, then watch whether the failure returns.
What is rubber duck debugging?
Rubber duck debugging is a technique of explaining code aloud, line by line, to any listener, even a rubber duck. Saying what each line should do often shows the developer the line where the code does something else.
Should the bug report name the root cause?
A bug report should name a suspected root cause only after the facts, which are the exact steps and the observed and expected results. Labeling the cause as a guess helps keep a wrong guess from steering the fix.