App Development

What is Debugging? Fixing Issues in Your Code

What is Debugging? Fixing Issues in Your Code

Undo’s 2020 report, produced with a Cambridge Judge Business School MBA project, puts the average time to fix one software failure at 13 hours.

Undo sells replay tooling, so read the figure as an estimate.

Still, that’s a long time for a single bug. The clock starts when a tester, a user or a monitoring alert reports that something broke, and it stops when a developer has traced the failure back to a defect in the code and removed it. That whole stretch, from reproduction through root cause analysis to a verified fix, is debugging.

What Is Debugging?

YouTube player

Testing shows that a failure exists. Debugging explains why it happens and fixes the reason, not only the symptom.

People mix up a few terms here, so it helps to pin them down. A defect (the bug) is the flaw itself, sitting in source code, configuration or design. What a user or a test actually observes, say a crash or a bad output, is the failure. The program may report that failure through an error message, often with a stack trace attached. Remove the root cause, meaning the defect whose removal stops the failure from coming back, and that particular problem is finished.

You end up with a verified fix and a passing regression check. A written note on the cause goes with them.

The work sits at the repair stage of software development, after a tester, a user or a monitoring alert reports that something broke.

Teams usually log the failure in a defect tracking system first, so the fix has a record to attach to.

A Cambridge Judge Business School MBA project reported in 2013 that developers spend 50% of their programming time finding and fixing bugs, a cost it projected at $312 billion a year worldwide.

A 2020 report from Undo and a Cambridge MBA project put the loss at roughly $61 billion and 620 million developer hours a year. The two figures differ about fivefold, and both come from MBA projects run with Undo, a vendor of reverse-debugging tools, so read them as order-of-magnitude estimates.

What is powering the mobile app revolution?

Explore app development statistics: market growth, platform preferences, monetization trends, and the data behind successful mobile applications.

Discover App Dev Data →

Hardware faults get debugged as well, but the methods covered here target software.

The Debugging Process, From Reproduction to Verified Fix

YouTube player

Published models disagree on how many steps there are, from three (Coursera) to six (IBM, 2024), and the gap comes from how finely they slice the same work. Reproduction comes first in IBM’s list and in Wikipedia’s account of the process.

  1. Reproduce the failure on demand, ideally as a minimal reproducible example.
  2. Isolate it to one module, function or commit.
  3. Find the root cause by explaining why the code behaves that way under those exact conditions.
  4. Fix it, and keep the change as small as the cause allows.
  5. Verify the fix by rerunning the failing case, then the unit and regression tests.
  6. Write down the cause and the fix where the next person will look.

Verification matters because a patch that hides the symptom without removing the root cause returns as a regression, which is what regression testing exists to catch.

IBM (2024) notes that most developers track fixes in version control, so an attempt that fails or breaks something else can be rolled back.

Reproduction is the stage developers say costs them most. 41% named it the biggest barrier to faster fixes in the Undo and Cambridge Judge Business School report (2020). Undo sells record-and-replay software, which colors the framing.

When the failure will not repeat, the sequence stalls at step one and different tactics apply.

Types of Bugs and How They Show Up

YouTube player

Bugs are easiest to sort by how they surface.

A missing closing parenthesis is the classic syntax error. The code breaks the grammar of the language, so the compiler or interpreter rejects it before the program runs, and linting flags most cases right in the editor.

Runtime errors happen during execution. Dividing by zero will do it, and so will calling a method on a null reference. They show up as exceptions or crashes, usually with a stack trace that names the failing line.

Logic errors are the sneaky ones. The code is valid and runs fine, but the result is wrong, like a loop that stops one item early. No error message appears, so nobody notices until someone checks the output.

Memory errors range from leaks to out-of-bounds reads and buffer overflows, and they’re most common in C and C++. Valgrind and AddressSanitizer exist to find them.

Concurrency errors, such as race conditions and deadlocks, depend on thread timing. They appear intermittently, so a single clean run proves little.

Heartbleed shows how little code a memory error needs. A missing bounds check in OpenSSL’s heartbeat handling (CVE-2014-0160) let a crafted request read up to 64 KB of server memory at a time, and OpenSSL 1.0.1g fixed it on 7 April 2014, according to OWASP and the CVE record.

Debugging Techniques and When Each One Fits

Match the technique to what you can observe.

Code you cannot pause usually gets print or log debugging, and code you can pause gets a debugger. Bisection is for regressions where you know a good version exists. Rubber duck debugging works when your own assumptions are the flaw, and record and replay is the one to reach for when a failure won’t repeat.

TechniqueBest forBreaks down whenTool example
Print and log debuggingCode you cannot pause, concurrent or distributed systemsOutput buries the signal or shifts timingLogging libraries
Interactive debuggingLogic errors in code you can pause locallyThe program cannot be paused, as in many production systemspdb, GDB, IDE debuggers
Rubber duck debuggingFlawed assumptions in your own codeThe cause sits in runtime data you cannot read off the pageA duck, or any listener
BisectionRegressions with a known good versionNo good version exists, or commits will not buildgit bisect
Record and replayFailures that will not repeat on demandNo Linux, or no hardware counters in the VMrr

Print and log debugging

YouTube player

Print debugging adds temporary output so you can read variable values and the order of events. You print values at suspect points and log function entry and exit. A timestamp on each line helps when order matters.

Honestly, it gets a worse reputation than it deserves. IBM (2024) names concurrent and distributed systems as its best fit, since execution order shapes behavior there.

The downsides are real, though. The same output can shift timing enough to hide the bug, and stray prints left in committed code turn into noise in production logs.

Interactive debugging and breakpoints

YouTube player

A breakpoint pauses execution at a line or function you pick. A conditional breakpoint stops only when an expression is true, so a long loop does not need a manual stop per iteration. Watchpoints stop when a value changes, which means no guessing where.

Then there’s stepping. Step over runs a line whole, while step into enters the function it calls.

Python 3.7 added the built-in breakpoint() function (PEP 553), which opens pdb at the call site. Setting PYTHONBREAKPOINT=0 disables every call at once, which keeps a forgotten breakpoint from freezing a production process.

Browser code follows the same pause-and-inspect model, as a walkthrough of debugging JavaScript shows.

Rubber duck debugging

YouTube player

The term comes from The Pragmatic Programmer (Andrew Hunt and David Thomas, 1999), which describes a programmer who explains code line by line to a rubber duck.

Saying the code aloud forces unstated assumptions into words, and the gap between what the code does and what you describe is often the bug.

It stops working when the cause sits in runtime data you cannot see by reading the code.

Bisection and divide and conquer

Bisection finds the change that broke something by halving the search range each round.

For code history, the git bisect command automates it. You mark one known good and one known bad commit, and Git checks out midpoints until it names the first bad one (Git documentation). Add git bisect run with a test script and the search finishes unattended, and git bisect skip sets aside commits that cannot be built or tested.

A range of 1,000 commits needs at most 10 test rounds. Skipping costs certainty, though: the Git documentation warns that Git may be unable to tell which of the bad commit and the skipped ones came first.

The same halving works inside one program. IBM (2024) calls it divide and conquer: test one function or module at a time, then split the guilty segment again.

Record and replay

Record and replay captures one run of a program and lets you step through it again, forward and backward.

On Linux, rr does this under GDB and adds reverse-continue, reverse-step and reverse-finish. Set a watchpoint on a corrupted value, run backward, and stop at the write that changed it.

rr started at Mozilla, and development is now sponsored by Pernosco (rr README). It needs hardware performance counters, which VMware and KVM guests are known to support and Xen guests do not (rr README).

Debuggers and Tools by Language and Platform

YouTube player

Start with the debugger your language runtime already ships with, then add a specialized inspector for the bugs it cannot see.

Every tool below pauses, inspects and steps. They differ mostly in where they attach and what they cost at runtime.

Language debuggers

  • GDB is the standalone option for native code, more powerful than IDE debuggers and harder to learn (IBM, 2024).
  • LLDB is the default debugger in Xcode, covering C, Objective-C and C++ on the desktop, on iOS devices and in the simulator.
  • Python’s standard-library debugger, pdb, enters post-mortem mode on its own when a script exits abnormally.
  • Chrome DevTools handles browser JavaScript, with line-of-code, conditional, DOM change, XHR/fetch and event listener breakpoints.

Python 3.14 added a -p option to pdb, so you can attach to a running process by its PID (Python documentation).

Node.js ships a V8 Inspector. Start the app with –inspect and attach Chrome DevTools, or use –inspect-brk to stop on the first line of application code (Node.js documentation).

IDE debuggers

IDEs bundle a debugger behind a debug mode. IBM (2024) names Visual Studio, Eclipse and PyCharm as examples.

Visual Studio Code ships one built-in debugger, for Node.js, and reaches other languages through debug extensions configured in launch.json. A guide to debugging Python in VSCode covers that setup for Python.

IntelliJ IDEA adds field, method and exception breakpoints for Java, plus remote debugging against a process already listening on a port (JetBrains documentation).

Memory and system inspectors

Valgrind’s Memcheck runs a program 20 to 30 times slower than normal and uses far more memory, according to the Valgrind quick-start guide. It also does not catch every memory error, by its own documentation.

AddressSanitizer costs about 2x in speed and uses more real memory than a native run (Clang documentation). It exits on the first detected error, so a run with several bugs needs several rounds.

AddressSanitizer is the cheaper default when you can rebuild the code. Valgrind earns its slowdown on binaries you cannot recompile.

Compile a debug build with -g so both tools report file names and line numbers instead of raw addresses.

Production error tracking

Production failures arrive as reports, not as live sessions.

Sentry collects each error from the production environment as an event and groups similar events into one issue, based on the stack trace when one is present (Sentry documentation).

Grouping uses only the stack frames the SDK reports for your application, so a decorator that adds a stack level changes how events group.

Is Debugging the Same as Testing?

YouTube player

No. Testing produces a defect report, and debugging turns that report into a verified fix.

DimensionTestingDebugging
Question askedIs something broken?Why is it broken?
Starts withPlanned test casesA reported failure
Typical ownerQA testers and developersDevelopers
OutputDefect report with reproduction stepsVerified fix and recorded root cause
AutomationRoutinePartial (fault localization, AI-assisted tools)

The two meet at the defect report, which is where the software testing lifecycle hands work to developers.

A software tester adds the most to debugging by writing down the reproduction steps plus the expected and the actual result.

Sources don’t fully agree on where the line sits. Coursera treats testing as one part of debugging, while Firmbee and Testsigma describe two separate activities.

On lifecycle, InterviewBit places debugging outside the SDLC as the end result of testing, while IBM (2024) calls the two complementary parts of building code.

Automation draws a split too. InterviewBit and Firmbee call debugging a manual task, while IBM (2024) lists automated debugging and Wikipedia describes automated fault localization.

Most of the disagreement comes from definitions, not practice. Read debugging as the work that starts after a failed test, and the conflicts shrink to wording.

When Standard Debugging Methods Fail

Breakpoints, logs and reproduction fail when a bug cannot be triggered on demand or changes when observed.

Heisenbugs

A heisenbug is a defect that changes or disappears when you try to observe it. Jim Gray’s 1985 paper on software failures used the term and described how a bug catcher can perturb the system just enough to hide the fault.

An optimizing compiler can produce the bug while the unoptimized debug build does not. Thread timing shifts too when a debugger or log line slows one thread.

Reproduce the bug in the build that fails, even if symbols get harder to read.

Race conditions

YouTube player

Leveson and Turner traced the six Therac-25 overdoses between June 1985 and January 1987 to race conditions in how the software read operator input (IEEE Computer, 1993).

Earlier models had hardware interlocks that masked the same software defects, so the flaw stayed hidden until the Therac-25 removed them.

The 2003 Northeast blackout involved a race condition in GE Energy’s XA/21 alarm software, and the US-Canada task force listed the resulting alarm failure among the direct causes. GE reproduced the crash only after slowing the system, injecting delays while feeding it alarm inputs, and the bug was unmasked about eight weeks after the blackout (The Register, 2004).

GE also reported more than three million online operational hours in which nothing had exercised the bug.

ThreadSanitizer automates the delay trick. It detects data races at a typical slowdown of 5x to 15x, and its optional adaptive delay feature injects delays at synchronization points to expose rare interleavings (Clang documentation, as of October 2026).

Failures that will not reproduce

When a failure will not reproduce locally, stop stepping through code and collect evidence from the environment where it fails.

  • Stack traces and logs from the failing system
  • Core dumps for post-mortem debugging of a crashed process
  • Error-tracking events with their stack traces
  • Remote debugging sessions into the failing system

The specific user environment and usage history can make a failure hard to reproduce, according to Wikipedia’s debugging entry. Environment parity between test and production setups removes one source of that gap.

Memory corruption far from the crash

In C and assembly, a bug can corrupt memory silently, and the crash shows up far from the original fault (Wikipedia). The stack trace points at the victim, not the culprit.

Memory debuggers exist for that gap. AddressSanitizer reports a bad access such as a heap use-after-free at the moment it happens, not at the later crash.

Where Did the Term Debugging Come From?

Debugging entered computing from aeronautics, and bug as a word for a defect is older than both fields.

Thomas Edison used “bugs” for the little faults and difficulties in his inventions as early as 1878, according to Wikipedia and the Centre for Computing History.

J. Robert Oppenheimer wrote to Ernest Lawrence at UC Berkeley on 27 October 1944 and used the word debugging while discussing the recruitment of technical staff. The Oxford English Dictionary cites a 1945 article in the Journal of the Royal Aeronautical Society that applies it to airplane engine testing.

In 1947, engineers on Harvard’s Mark II found a moth in a relay and taped it into the logbook with the label “first actual case of bug being found.”

The Smithsonian’s National Museum of American History, which holds the logbook, says it was probably not Grace Hopper’s. It credits her and the Mark II team with popularizing bug and debug.

The ACM digital library first uses debugging in papers from the 1952 ACM National Meetings, two of them putting the word in quotation marks. Gill’s 1951 EDSAC paper, the earliest in-depth discussion of programming errors, uses neither bug nor debugging.

By 1963 the CTSS manual used debugging on page 1 without explaining it.

The popular telling credits Hopper with finding the moth and coining the word. The entry’s wording shows the engineers already knew bug as slang (JSTOR Daily), and the 1944 letter predates the moth by three years, so the moth is an anecdote, not the origin.

Debugging FAQ

Why does debugging matter in software development?

Testing only reports that a failure exists, so without debugging the defect stays in the product and keeps failing. Wikipedia defines the work as finding the root cause, workarounds and possible fixes, which is what separates a repair from a ticket.

Does debugging also apply to hardware?

Yes. Hardware and low-level software such as BIOSes, device drivers and firmware are debugged with oscilloscopes, logic analyzers and in-circuit emulators, according to Wikipedia.

An in-circuit emulator can perform many of a software debugger’s tasks on firmware.

Is debugging the same as troubleshooting?

No. Troubleshooting diagnoses why a whole system misbehaves, and the cause may be a setting, a network link or a failed component. Debugging is the narrower job of finding and fixing a defect in code.

Who does the debugging, developers or testers?

Developers do most of it, because the fix lives in their code. Testers supply the reproduction steps and the expected versus actual result.

The word debugger can also mean the person doing the work, per Wikipedia.

What is remote debugging and when is it needed?

Remote debugging means debugging a program that runs on a different system from the debugger, connected over a link such as a local area network (Wikipedia). You need it when the failure only exists on a server, a device or a customer machine.

IntelliJ IDEA, for example, attaches to a process already listening on a port.

How is debugging in production different from development?

In production you cannot pause a live service without blocking real users. Evidence comes from logs, error events and core dumps instead of breakpoints.

A fix also ships as a release, not a local rebuild.

What is automated or AI-assisted debugging?

Automated debugging uses tools that locate or repair faults with less manual effort. Fault localization flags the statements most likely to hold the defect, and some approaches use machine learning to improve accuracy (Wikipedia).

Automatic program repair generates patches meant to avoid regressions, so a generated fix still needs a regression run.

What is the difference between static and dynamic debugging?

Static debugging examines source code without running it, while dynamic debugging observes the program as it executes. Static analysis tools look for known problem patterns, such as a variable used before it is assigned, and have a reputation for false positives (Wikipedia).

Dynamic methods include breakpoints, tracing and watchpoints.

Getting Faster at Debugging

Speed mostly comes from shortening the time between a failure and its first piece of evidence. Teams get there by fixing the order of work and automating the cheap checks.

Shrink the failing case first and run a sanitizer on every test build. Bisect once a known good version exists.

The order puts the cheapest evidence first and the costliest search last.

By the documented overheads, AddressSanitizer costs roughly 10 to 15 times less per run than Valgrind’s Memcheck, which is why it fits every build.

The trade-off is memory. ThreadSanitizer adds a typical 5x to 10x memory overhead and still carries a beta label in the Clang documentation as of October 2026, so reserve it for test runners, not production hosts.

Bogdan Sandu

Stay sharp. Ship better code.

Every week: one curated article, one tool worth knowing, one tip you can use tomorrow. No noise, no padding.