Debug Python Code with `pdb`

Learn how to use Python's built-in pdb debugger to step through code, set breakpoints, inspect variables, and find bugs interactively.

7 min read

Print statements work for simple bugs. When a value is wrong, you add a print, run the code, and read the output. But when the bug is in a loop, a nested function call, or a conditional that rarely triggers, print statements become slow and frustrating. You add five prints, run the code, realize you need three more, and repeat.

Python's built-in debugger, pdb, lets you pause execution at any line, step through code one statement at a time, inspect every variable, and change values while the program is running. You can answer questions like "what is the value of x at this exact moment" and "did this if branch actually execute" without adding or removing any code beyond a single breakpoint. It is especially useful once a test from testing exceptions and edge cases starts failing and you need to see why.

Dropping into the debugger

The fastest way to use pdb is to insert a breakpoint directly in your code. Add one line at the spot you want to inspect, then run your program normally:

pythonpython
def calculate_total(prices, discount):
    subtotal = sum(prices)
    breakpoint()
    if discount > 0:
        subtotal *= (1 - discount)
    return subtotal

When Python reaches the breakpoint, execution pauses and control switches from your program to the debugger. The pdb prompt shows the current file and line:

texttext
> my_script.py(4)calculate_total()
-> if discount > 0:
(Pdb)

From here you can inspect variables, step through code, or continue running.

The older syntax import pdb; pdb.set_trace() works identically. Use breakpoint() in new code because you can disable all breakpoints by setting the environment variable PYTHONBREAKPOINT=0 without editing any files.

Essential pdb commands

Once you are at the pdb prompt, a small set of commands covers most debugging sessions. Each command has a short one-letter or two-letter abbreviation.

Type n (next) to execute the current line and stop at the next line in the same function. This is your primary movement command when you want to see what happens line by line:

texttext
(Pdb) n
> my_script.py(5)calculate_total()
-> subtotal *= (1 - discount)

Type s (step) to step into a function call. If the next line calls another function, step takes you inside that function so you can debug it too. Next would run the entire function and stop afterward.

Type c (continue) to run until the next breakpoint or until the program ends. Use this when you have seen enough of the current section and want to jump ahead.

Type p followed by a variable name to print its current value:

texttext
(Pdb) p subtotal
150.0
(Pdb) p discount
0.15

Type pp instead of p for pretty-printed output of complex objects like dictionaries and nested lists. Type l (list) to see the surrounding source code with an arrow pointing to the current line.

Type q (quit) to exit the debugger and abort the program. Use this when you have identified the bug and are ready to fix the source code.

Setting breakpoints from inside pdb

You do not need to edit your source file to add breakpoints. From the pdb prompt, type b followed by a line number to set a breakpoint at that line in the current file:

texttext
(Pdb) b 12
Breakpoint 1 at /path/to/my_script.py:12

Type b with a function name to break at the start of that function. Type b with no arguments to list all current breakpoints. Type cl (clear) followed by a breakpoint number to remove it.

Conditional breakpoints stop only when a condition is true. This is useful when a bug only appears inside a loop with specific data:

texttext
(Pdb) b 15, item == "target_value"
Breakpoint 2 at /path/to/my_script.py:15

The breakpoint only triggers when the variable item equals "target_value". Without this condition, you would have to manually step through every iteration until the right one appears.

Post-mortem debugging

When your program crashes with an unhandled exception, pdb can take you to the exact line where the exception occurred. Run your script with the -m pdb flag and pdb stops at the failing line, letting you inspect the state at the moment of failure:

bashbash
python -m pdb my_script.py

You can print variables, examine the call stack, and understand why the crash happened without adding any breakpoints beforehand.

For exceptions that occur in a running Python session, use pdb.pm() to enter post-mortem debugging of the most recent exception. This takes you to the exact line where the crash occurred:

pythonpython
>>> 1 / 0
ZeroDivisionError: division by zero
>>> import pdb; pdb.pm()
> <stdin>(1)<module>()
(Pdb) 

When your code has multiple layers of function calls, use w (where) to see the full call stack. The arrow points to the current frame:

texttext
(Pdb) w
  /path/to/main.py(10)<module>()
-> result = process(data)
  /path/to/utils.py(5)process()
-> return validate(items)
> /path/to/utils.py(12)validate()
-> if not item:

Type u (up) to move to the calling function and inspect its variables. Type d (down) to move back. This lets you trace how data flows through multiple function calls without restarting the debugger.

A complete debugging session

Imagine a function that calculates shipping cost but returns zero for certain weights. You suspect the discount logic is wrong but are not sure where:

pythonpython
def shipping_cost(weight, is_member):
    base = weight * 2.5
    if is_member:
        base = base * 0.8
    if weight > 10:
        base = 0
    return base

Insert breakpoint() on the line just before the weight check, run the program with real input, and step through the logic to watch how base changes:

texttext
> shipping.py(6)shipping_cost()
-> if weight > 10:
(Pdb) p weight
12
(Pdb) p is_member
True
(Pdb) p base
24.0
(Pdb) n
> shipping.py(7)shipping_cost()
-> base = 0
(Pdb) 

In three commands you identified the bug: the weight threshold resets the cost to zero instead of applying a different rate. You found what would have taken several print-debug cycles in under a minute with pdb.

The next article covers debugging Python programs in VS Code, which gives you the same debugging power with a visual interface, breakpoints you set by clicking, and variable values displayed in a panel.

Rune AI

Rune AI

Key Insights

  • Insert breakpoint() or import pdb; pdb.set_trace() at any line to drop into the debugger when that line executes.
  • n (next) steps over function calls; s (step) steps into them; c (continue) runs until the next breakpoint.
  • p variable prints the value of a variable; pp variable pretty-prints complex objects; l lists surrounding source code.
  • b lineno sets a breakpoint at a specific line; b function_name sets one at the start of a function.
  • q quits the debugger and aborts the program; use it when you have found the bug and are ready to fix the code.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between pdb.set_trace() and breakpoint()?

breakpoint() is a built-in function added in Python 3.7 that calls pdb.set_trace() by default. The advantage of breakpoint() is that you can disable all breakpoints by setting the PYTHONBREAKPOINT environment variable to 0, which is safer than accidentally leaving pdb.set_trace() calls in production code. In practice, both do the same thing: they drop you into the debugger at the line where they are called.

How do I debug a script that reads from standard input?

pdb reads commands from stdin, which conflicts with scripts that also read from stdin. The workaround is to run the script under pdb using python -m pdb script.py and set breakpoints before the input-reading code. The debugger prompt will appear, and you can use continue to run until the breakpoint. For scripts that need complex stdin interaction, consider using an IDE debugger or redirecting input from a file.

Can I debug a Python program that is already running?

Yes, in Python 3.14 and later, you can attach pdb to a running process using python -m pdb -p PID. This sends a signal to the process and drops you into the debugger at the next bytecode instruction. For earlier versions, you can use third-party tools like pyrasite or madbg to achieve the same result.

Conclusion

pdb turns debugging from a guessing game into a step-by-step investigation. Set a breakpoint, run your code, and walk through it one line at a time while watching the variables change. The few minutes you spend learning pdb commands will save hours of adding and removing print statements.