Historical Context & Motivation
If you have ever added a dozen print() statements throughout your code just to figure out why a variable has the wrong value, you already understand the problem that debuggers were invented to solve. Early programmers literally had to trace through machine instructions by hand, sometimes reading punched cards one at a time. As programs grew larger and more complex, the need for automated tools to pause execution and examine a program's internal state became critical.
The word "bug" in computing dates back to the 1940s, when a moth was found stuck in a relay of the Harvard Mark II computer. Since then, the concept of debugging — the systematic process of finding and removing errors — has evolved from hand-tracing to sophisticated interactive tools. Python's built-in debugger, pdb, and modern IDE debuggers (like those in VS Code or PyCharm) give you the power to freeze your program mid-run and look under the hood.
So the central question is: how can you move beyond scattering print() statements and instead use a structured, interactive approach to find bugs? That is exactly what breakpoints, stepping, and inspecting are designed to do.
Core Principles of Debugging
Debugging with pdb or an IDE revolves around three fundamental actions. These three concepts form the backbone of every debugging session, whether you are using a simple terminal-based tool or a full graphical IDE. Understanding them turns you from someone who guesses at bugs into someone who systematically hunts them down.
Breakpoint
Step
Inspect
Continue
print() debugging feels like.Visual Explanation — The Debugging Workflow
The diagram below illustrates the typical debugging workflow. You start by writing code, then set a breakpoint at a suspicious line. When you run the program in debug mode, execution pauses at that line. From there you can inspect variables, step through lines one at a time, or continue to the next breakpoint. This cycle repeats until you find and fix the bug.
Notice that debugging is a cycle, not a straight line. After you step through a few lines and inspect variables, you might continue running to the next breakpoint, find more clues, and step again. The key insight is that at every pause, you have complete access to your program's state — every variable, every data structure, and even the order of function calls that brought you to that point.
How pdb and IDE Debuggers Work
Python's pdb module is a text-based interactive debugger that comes built into Python's standard library. You do not need to install anything — it is always available. There are two main ways to activate it in your code.
Method 1: The breakpoint() Function (Python 3.7+)
Simply write breakpoint() on any line of your program. When Python reaches that line, it drops you into the pdb interactive prompt, where you can type commands to inspect and step through your code.
Method 2: import pdb; pdb.set_trace()
Before Python 3.7, the standard way was to write import pdb; pdb.set_trace() at the line where you wanted to pause. This does the same thing as breakpoint() but requires the explicit import. You will see both in online tutorials, and they are functionally equivalent.
Essential pdb Commands
| Command | Shortcut | What It Does |
|---|---|---|
next | n | Execute the current line and move to the next one (Step Over). |
step | s | Step Into the current function call to see what happens inside it. |
continue | c | Resume normal execution until the next breakpoint is hit. |
print(var) | p var | Print the current value of a variable (Inspect). |
list | l | Show the source code around the current line. |
quit | q | Exit the debugger and stop the program. |
IDE Debugging: The Graphical Approach
If you use an IDE like VS Code or PyCharm, you get the same breakpoint/step/inspect capabilities but with a graphical interface. Instead of typing commands, you click in the margin next to a line number to set a breakpoint (usually shown as a red dot). You then press a "Run and Debug" button, and when execution pauses, a panel shows you all current variables, the call stack (the chain of function calls), and toolbar buttons for Step Over, Step Into, Step Out, and Continue. The concepts are identical to pdb — the interface is just more visual.
Stepping Modes Explained
One of the trickiest parts of debugging is understanding the difference between the various stepping modes. When your program calls a function, should the debugger jump inside that function, or treat it as a single operation and move on? The diagram below clarifies the three stepping options.
return (shortcut r). In IDE debuggers like VS Code, it is a button with an upward arrow icon labeled "Step Out." The idea is the same: finish running the current function and go back to wherever it was called from.Worked Example — Debugging a Buggy Function
Let us walk through a realistic debugging session. Consider this function that is supposed to compute the average of a list of numbers, but it returns the wrong answer.
Here is the buggy code:
def average(numbers):
total = 0
for num in numbers:
total += num
return total / len(numbers) + 1 # Bug is here!
When we call average([10, 20, 30]), we expect 20.0 but get 21.0. Let us debug it step by step using pdb.
breakpoint() at the top of the function, right after def average(numbers):. This will pause execution as soon as the function is called.def average(numbers):
breakpoint()
total = 0
...python my_script.py. The program starts and immediately pauses at the breakpoint. You see the pdb prompt: (Pdb).> my_script.py(3)average()
-> total = 0
(Pdb)p numbers to print the value of the numbers parameter. This confirms the function received the correct input.[10, 20, 30] — looks correct.n repeatedly to step over each line. After the loop finishes, type p total to check the accumulated sum.60 — that is correct (10 + 20 + 30 = 60).p total / len(numbers) + 1 to evaluate the expression. Python's order of operations calculates 60 / 3 first (getting 20.0) and then adds 1, giving 21.0. The + 1 is the bug! It should not be there, or it needs parentheses.+ 1 from the return statement. The correct line is return total / len(numbers).breakpoint() call so your program runs normally in production. Leaving breakpoints in shipped code is a common beginner mistake!pdb vs IDE Debugging vs print()
Now that you understand both approaches, let us compare them directly. Each method has its place, and experienced developers use all three depending on the situation.
| Feature | print() Debugging | pdb (Terminal) | IDE Debugger |
|---|---|---|---|
| Setup required | None — just add print() | None — built into Python | Need an IDE installed |
| Pause execution? | No — program runs to the end | Yes — pauses at breakpoints | Yes — pauses at breakpoints |
| Inspect variables | Only what you print | Any variable, any expression | Any variable, with a GUI panel |
| Step through code | Not possible | Yes — n, s, r commands | Yes — toolbar buttons |
| Learning curve | Very easy | Moderate — need to learn commands | Moderate — need to learn the UI |
| Best for | Quick checks, simple scripts | Remote servers, any environment | Complex projects with many files |
print() debugging like shining a flashlight in a dark room — you can only see where you point it. A proper debugger is like turning on all the lights at once. You see everything: every variable, every function call, every piece of data. Use print() for quick checks, but reach for pdb or your IDE debugger when the bug is tricky or you do not even know where to start looking.Connection to Advanced Debugging
The breakpoint/step/inspect cycle you learned here is the foundation, but professional debugging tools offer even more power. As you advance, you will encounter these more sophisticated features.
| Introductory Concept | Advanced Version |
|---|---|
| Simple breakpoints (pause at a line) | Conditional breakpoints — pause only when a condition is true, e.g., only when x > 100 |
| Inspect one variable at a time | Watch expressions — automatically track how a value changes over time |
| pdb in the terminal | Remote debugging — connect your IDE to code running on a different machine or server |
| Step through line by line | Post-mortem debugging — examine the program state at the exact moment an unhandled exception occurred |
| Manual breakpoint placement | Logging frameworks — strategic, permanent instrumentation using Python's logging module |
You do not need to learn all of these right now. The important thing is that mastering breakpoints, stepping, and inspecting gives you a transferable skill. Every programming language — JavaScript, Java, C++, Go — has debuggers with these same three concepts. Once you internalize them in Python, you can debug anywhere.
Practice Problems
print() statement when debugging. Why might a breakpoint be more useful than a print statement for finding complex bugs?breakpoint() on line 3:
x = 5
y = x * 2
breakpoint()
z = y + 3
print(z)
When the debugger pauses, what values will p x and p y show? What about p z?def multiply(a, b):
return a * b
def process(values):
breakpoint()
result = 1
for v in values:
result = multiply(result, v)
return result
print(process([2, 3, 4]))
You want to see what happens inside multiply() during the first loop iteration. What pdb commands would you type, in order, to get there?def count_vowels(text):
count = 0
vowels = 'aeiou'
for char in text:
if char in vowels:
count + 1
return count
Describe how you would use pdb to identify the bug. Which line would you place the breakpoint on, and which variable would you inspect after each iteration?breakpoint() to debug a live web server. What alternatives from the "Advanced Debugging" section (Section 8) would be more appropriate, and why?Summary
Python provides powerful debugging tools that go far beyond print() statements. The built-in pdb module and modern IDE debuggers both revolve around three core actions: setting breakpoints to pause execution at a specific line, stepping through code one line at a time (using Step Over, Step Into, and Step Out), and inspecting variables and expressions while the program is paused.
You can activate pdb using breakpoint() (Python 3.7+) or import pdb; pdb.set_trace(). The essential commands are n (next / step over), s (step into), c (continue), p (print / inspect), and q (quit). These same concepts apply to every IDE debugger and transfer to every programming language, making this one of the most universally useful skills in computer science.