Tutorial

Master pdb: Python's Built-in Debugger

Learn to use pdb, Python's standard library debugger, including breakpoints, post-mortem debugging, and essential commands to replace print-based debugging.

July 2026 8 min read 10 views 0 hearts

The Debugger You Already Have: pdb in Python

Every Python developer hits that moment when print() statements just aren't cutting it anymore. You're staring at a traceback that makes no sense, or your code works fine until that one specific input breaks everything. The natural instinct is to sprinkle print() everywhere, but there's a better tool sitting right there in Python's standard library: pdb, the Python Debugger.

I remember spending an entire afternoon debugging a Django application at Pythonskillset, going through dozens of iterations of print statements, only to discover I was modifying a list that was being shared across function calls. pdb would've caught that in minutes.

What Makes pdb Different?

Think of pdb as a pause button for your code. Instead of guessing what your variables contain at certain points, you can freeze execution, inspect anything, and step through line by line. It's like having x-ray vision into your running program.

Getting Started With pdb

The simplest way to use pdb is to drop a breakpoint right where you want to investigate. There are two flavors:

# Old school (works in Python 2 and 3)
import pdb; pdb.set_trace()

# Modern Python (3.7+)
breakpoint()

The breakpoint() function is cleaner and respects the PYTHONBREAKPOINT environment variable, which means you can disable all breakpoints without changing code.

The Really Useful Commands

Once pdb stops your code, you get a (Pdb) prompt. Here's what actually matters:

n (next) - Executes the current line and moves to the next one in the same function. This is your bread and butter.

s (step) - Steps into function calls. If you're on a line that calls another function, s takes you inside it.

c (continue) - Runs the program until the next breakpoint. Perfect when you've seen enough of this section.

l (list) - Shows where you are in the code with context. The current line is marked with ->.

p (print) - Shows the value of any expression. p my_variable, p len(data), whatever you need.

pp (pretty print) - Same as p but formats complex objects like dictionaries and lists.

q (quit) - Exits the debugger and terminates the program.

A Real Example

Let me show you something that actually happened during a Pythonskillset code review:

def calculate_averages(scores):
    total = 0
    for student in scores:
        total += sum(student['grades'])
    return total / len(scores)

This looks innocent enough, but it crashes when a student has no grades. With pdb:

def calculate_averages(scores):
    total = 0
    breakpoint()  # Stop right here
    for student in scores:
        total += sum(student['grades'])
    return total / len(scores)

Now you can inspect scores, check if any student is missing the 'grades' key, and understand exactly what's going wrong. You can even type len(student['grades']) at the pdb prompt to see what happens.

The Hidden Gem: Post-Mortem Debugging

This is my favorite pdb feature. When your code crashes, instead of just staring at the traceback:

try:
    run_risky_operation()
except:
    import pdb; pdb.post_mortem()

This drops you into debugger mode right at the crash site. All your variables are preserved exactly as they were when the exception happened. It's like being a detective who arrives at the crime scene before anyone touches anything.

Setting Breakpoints Strategically

Don't just put breakpoints everywhere. Think about what you're trying to discover:

  1. At function start - To check if arguments are what you expect
  2. Before an exception - Right before a line that keeps erroring
  3. In loop iterations - To see if data changes across iterations
  4. At conditional branches - To verify your if/else logic

The Trade-offs

pdb slows things down, obviously. You're not going to debug a production web server with it. But for development and testing, it's invaluable. The biggest advantage over print statements is that you're not committing debug code or dealing with the mess of removing it later.

Beyond the Basics

Once you're comfortable, explore these:

  • Conditional breakpoints: breakpoint() inside an if statement
  • Watchpoints: Monitor when a variable changes value
  • Remote debugging: pdb over network connections for headless systems

pdb isn't flashy, but it's been solving real debugging problems for decades. Next time you reach for print(), consider whether pdb might save you more time. At Pythonskillset, we've found that developers who learn pdb early spend less time debugging and more time building.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.