Use a Debugger, Not Print — Debugging Systematically, Part 4
Updated: Aug 6
Debugging Systematically · Part 4
Adding print statements works — until you're on the fifth round of adding one, re-running, reading, and adding another. A debugger lets you stop the program mid-flight and inspect anything, without editing code and re-running each time. This walks through actually using one: breakpoints, stepping, conditional breaks, and post-mortem debugging — plus the cases where a print really is the right tool.
Print statements work. Debuggers work better. Inspect any state, any time, without modifying code.
Step 1: Set a Breakpoint (5 min)
Python 3.7+:
def process(data):
breakpoint() # execution pauses here
return data.cleaned()
Run normally. When breakpoint() is hit, drops into the debugger (pdb).
(Pdb) p data
{'foo': 'bar'}
(Pdb) p data.cleaned()
{'foo': 'BAR'}
(Pdb) c # continue
Step 2: pdb Basics (10 min)
Once in pdb:
n # next line
s # step into function
c # continue
l # list source around here
p var # print value
pp var # pretty-print
w # where (stack trace)
u / d # up / down stack frames
q # quit
Most useful: n, s, c, p, w.
Step 3: ipdb / pdb++ (5 min)
pip install ipdb
# Replace breakpoint() with:
import ipdb; ipdb.set_trace()
# Or set: PYTHONBREAKPOINT=ipdb.set_trace
Better UI: syntax highlighting, tab completion, sticky mode (shows source as you step).
Step 4: IDE Debuggers (10 min)
VS Code, PyCharm, others:
Click the line number to set a breakpoint
F5 to start debugging
F10: step over
F11: step into
Variables shown in sidebar; no p commands needed
Click "Watch" to track expressions
Easier than pdb for complex debugging. Use it.
Step 5: Conditional Breakpoints (10 min)
if user.id == 42:
breakpoint()
Or in IDE: right-click breakpoint, set condition user.id == 42.
Breaks only when the condition holds. For "the bug only happens for one user," this is invaluable.
Step 6: Post-Mortem (10 min)
When an exception fires:
import pdb
try:
do_something()
except:
pdb.post_mortem()
Drops you into the frame where the exception was raised. Inspect every variable as if it were live.
Or:
python -m pdb -c continue myscript.py
# Crashes → drops into pdb at the failure point
Step 7: Remote Debugging (10 min)
For services running in containers / production:
# remote_pdb
pip install remote_pdb
from remote_pdb import RemotePdb
RemotePdb('0.0.0.0', 4444).set_trace()
telnet container 4444
Now you can attach to a running container. Useful for production-debugging (carefully).
VS Code supports remote debugging via debugpy.
Step 8: Logging vs. Debugger (10 min)
Use the debugger for:
Local reproduction of bugs
Understanding control flow
Inspecting complex state
Use logging for:
Production issues you can't reproduce
Long-running processes you want to observe
Multi-process / multi-user systems
Both have their place. Choose deliberately.
Step 9: Watchpoints / Tracing (5 min)
For "when does X change?":
import sys
def trace(frame, event, arg):
if event == "line" and frame.f_locals.get("x") != last_x:
print(f"x changed: {frame.f_locals['x']}")
return trace
sys.settrace(trace)
Or use a debugger's watchpoint (often called "data breakpoint"). Tells you when a variable changes.
Step 10: Print Statements Still Have a Place (5 min)
print(f"DEBUG: user={user}, action={action}")
For:
Quick checks where setting up a debugger is overkill
Logging in test code
"Did this code path actually run?"
Don't commit them. Use logging.debug() if you want to keep them.
What You Just Did
Debugger basics: breakpoints, stepping, post-mortem, conditional, remote, vs. logging. The right tool for the right debugging task.
Common Failure Modes
Always print, never debugger. Slow iteration; modifies code; commits leak.
Always debugger, never log. Can't debug production.
Forgetting to remove debug code. breakpoint() ships to prod; service hangs on calls.
Step through 500 lines. Use breakpoints / conditional / post-mortem.
Treat debugger as "magic." Read its commands; it's just a UI.
Continue the Debugging Systematically path
Previous — Part 3: Read Stack Traces Effectively
Part of the Debugging Systematically learning path.


