A debugger is a program that runs another program under controlled conditions so that a developer can pause it at chosen points, execute it step by step and inspect or change its state, including variables, memory, registers and the call stack. Its purpose is to locate the cause of a defect by observing what the code actually does rather than what it was expected to do.
Among development tools, the debugger complements the IDE, the linter and version control systems such as Git. A linter finds suspicious code without running it, and tests reveal that something fails; the debugger explains why and where the behavior goes wrong. Common examples are GDB and LLDB for native code, the debuggers built into IDEs, and the developer tools of web browsers.
How a debugger controls a running program
A debugger relies on support from the operating system or the runtime to take control of the target process. On Linux, native debuggers use the ptrace system call; on Windows they use the debugging API of the operating system. Managed runtimes such as the JVM, .NET and JavaScript engines expose their own debugging interfaces instead.
A software breakpoint in native code is usually implemented by temporarily replacing an instruction with a trap instruction, such as INT 3 on x86 processors. When execution reaches it, the processor raises an exception, the operating system suspends the program and hands control to the debugger, which restores the original instruction when execution resumes.
To show source lines and variable names rather than raw addresses, the debugger needs debug symbols. Compilers emit them in formats such as DWARF on Linux and macOS or PDB files on Windows, mapping machine instructions back to files, lines, functions and variable locations.
Core debugger features
Every mature debugger offers a similar set of controls. Mastering them, especially conditional breakpoints and watchpoints, often shortens an investigation from hours to minutes. The table lists the essential features and their purpose.
| Feature | What it does | Typical use |
|---|---|---|
| Breakpoint | Pauses execution at a line or function | Stop where the problem is suspected |
| Conditional breakpoint | Pauses only when an expression is true | Catch the one failing iteration of a loop |
| Watchpoint | Pauses when a variable or memory changes | Find who overwrites a value |
| Step over, into, out | Executes one line, enters or leaves a call | Follow control flow precisely |
| Call stack view | Shows the chain of active function calls | Understand how execution got here |
| Variable inspection | Displays and edits current values | Compare actual and expected state |
| Logpoint | Prints a message without stopping | Trace behavior without editing code |
Hardware support makes watchpoints practical. Modern x86 processors provide four debug registers for hardware breakpoints and watchpoints, so a small number of memory locations can be monitored at full speed. Beyond that limit, debuggers fall back to slow software methods.
Types of debuggers and debugging modes
Debuggers differ in where the program runs and when the analysis happens. Some attach to a live process on the same machine, some connect over a network, and some work after the fact on a recorded failure.
| Mode | How it works | Best suited for |
|---|---|---|
| Local interactive | Debugger starts or attaches to a local process | Everyday development |
| Remote debugging | Debugger connects to a process on another host | Containers, servers, embedded devices |
| Post-mortem analysis | Examines a core dump or crash dump | Crashes in production |
| Record and replay | Records execution, replays it deterministically | Rare and timing-dependent bugs |
| Hardware debugging | Uses a JTAG or SWD probe on the chip | Firmware and microcontrollers |
Record and replay debuggers also enable reverse execution: a developer can run backward from a crash to the moment a value first became wrong. This is especially valuable for intermittent failures that refuse to reappear under normal debugging.
Debugger compared with other diagnostic techniques
A debugger is one of several ways to understand program behavior, and experienced developers combine them. The key difference is that a debugger offers deep, interactive insight into one execution, whereas logging and profiling summarize behavior over time.
| Technique | Strength | Limitation |
|---|---|---|
| Debugger | Full state at any chosen moment | Pausing disturbs timing |
| Logging or print statements | Works in production, shows sequences | Requires code changes and foresight |
| Profiler | Shows where time and memory go | Does not explain wrong results |
| Linter and static analysis | Finds defects without running code | Misses runtime-only problems |
| Automated tests | Detect regressions repeatedly | Show that it fails, not why |
Pitfalls and good practice
The most common surprise is that optimized builds are hard to debug. Compiler optimizations reorder instructions, inline functions and keep variables only in registers, so the debugger may skip lines or report values as unavailable. Debug builds, for example with -O0 -g in GCC and Clang, avoid this at the cost of speed.
Pausing a program also changes its timing. Race conditions and timeouts can disappear under the debugger, a behavior often called a heisenbug. For such defects, logpoints, tracing or record and replay are better choices than ordinary breakpoints.
- Form a hypothesis first and place breakpoints to test it, instead of stepping through code at random.
- Reproduce the defect reliably before debugging, ideally with a failing automated test.
- Keep debug symbols for release builds in a separate store so crash dumps remain readable.
- Never leave debug ports open on production systems, because a debugger can read and modify memory.
The short version
A debugger is a tool that lets a programmer pause a program and look inside it while it runs. It works like a slow-motion replay in sports. You can stop at any moment, move forward one step at a time and check every detail.
Without it, a programmer sees only that something went wrong at the end. With it, they can watch the exact moment a value turns wrong and see which line caused it. That makes fixing the mistake much faster.
Where it makes a difference
A developer at an online travel agency receives reports that the total price of a booking is sometimes a few cents too high. Unit tests pass, and the error appears only for certain combinations of hotels and discounts.
The developer reproduces one failing booking locally and sets a conditional breakpoint in the price calculation that triggers only for that booking ID. Stepping through the loop that adds room prices, the variable view shows that each discount is rounded separately before being added, instead of rounding once at the end.
The fix takes one line: totals are now computed in whole cents and rounded once. A new test with the problematic combination is added, and the investigation, which could have taken days of adding print statements, is finished in under an hour.