Debugging & Performance

Setting Conditional Breakpoints in pdb

You are stepping through a loop that processes 50,000 records and the bug only shows on one of them. A plain breakpoint stops on every iteration, so you hammer continue hundreds of times before reaching the interesting one — or give up. A conditional breakpoint stops only when a predicate is true, dropping you into the prompt on the exact iteration where the invariant breaks.

Prerequisites

  • Python 3.7+ for breakpoint(); the b, tbreak, and condition commands work in every pdb version.
  • Familiarity with entering the debugger and the basic command loop from interactive debugging with pdb and ipdb.

Solution

There are two ways to make pdb stop conditionally: a line breakpoint with an inline condition, and a guarded breakpoint() call in source. Start with the command form, which requires no source edits.

Python
# orders.py
def process(orders):
    total = 0
    for i, qty in enumerate(orders):
        total += qty          # we suspect a negative qty corrupts the total
    return total

if __name__ == "__main__":
    process([5, 3, 8, -2, 9, 4])
Bash
$ python -m pdb orders.py
(Pdb) b orders.py:5, qty < 0     # break at line 5 ONLY when qty is negative
Breakpoint 1 at orders.py:5
(Pdb) c                          # run; pdb skips every iteration until the predicate holds
> orders.py(5)process()
-> total += qty
(Pdb) p i, qty                   # we land exactly on the bad iteration
(3, -2)

The condition is any expression valid in the target frame; pdb evaluates it on every hit and stops only when it is truthy. To attach a condition to an existing breakpoint, or change one, reference the breakpoint by its number:

Bash
(Pdb) b orders.py:5             # unconditional breakpoint, gets number 1
Breakpoint 1 at orders.py:5
(Pdb) condition 1 qty < 0       # retrofit the predicate onto breakpoint 1
(Pdb) condition 1               # omit the expression to clear it again

For a breakpoint you only ever want to hit once — the common case inside a hot loop — use tbreak. It fires a single time and removes itself, so there is no leftover breakpoint to disable afterward:

Bash
(Pdb) tbreak orders.py:5, qty < 0    # one-shot: auto-removed after the first hit
Breakpoint 2 at orders.py:5
(Pdb) c
> orders.py(5)process()
-> total += qty
(Pdb) c                              # continues to the end; breakpoint is already gone

When you would rather express the predicate in source — for example because the condition is expensive or spans multiple statements — guard a breakpoint() call with an if:

Python
def process(orders):
    total = 0
    for i, qty in enumerate(orders):
        if qty < 0:               # only enter the debugger on the offending value
            breakpoint()
        total += qty
    return total

You can also raise the hit count with ignore: ignore 1 100 tells pdb to skip the next 100 hits of breakpoint 1 before honouring it — useful when you know the bug is "around iteration 100" but cannot express it as a value predicate.

Why this works

pdb stores conditions on the breakpoint object and evaluates them inside the paused frame's namespace via eval, so any in-scope name or expression is fair game. Because evaluation happens on every hit, a condition is functionally identical to a guarded breakpoint() — the command form just keeps the predicate out of your source. tbreak is a normal breakpoint with a temporary=True flag that pdb clears on first fire, and ignore decrements a counter before the condition is even checked.

Edge cases and failure modes

  • A condition that raises (e.g. qty < 0 when qty is sometimes None) makes pdb treat the breakpoint as hit and stops anyway — guard with isinstance checks inside the expression if the variable's type varies.
  • Conditions evaluate in the target frame, not where you typed them; a name that exists in your prompt frame but not the breakpoint's frame raises NameError on every hit.
  • Side-effecting conditions run on every hit — never put a mutation or a print in a condition; use commands for that instead.
  • tbreak with a condition still consumes its single life on the first time the condition is true, not the first time the line is reached.
  • Conditional breakpoints add per-hit overhead; on a million-iteration loop the eval cost is noticeable, so prefer a guarded breakpoint() or ignore count when the predicate is hot.

Frequently Asked Questions

How do I make pdb break only when a variable has a specific value? Set the breakpoint with a condition: b orders.py:42, qty < 0. pdb evaluates the expression in the target frame on every hit and only stops when it is truthy, so the prompt opens on the iteration where qty goes negative.

What is the difference between break and tbreak in pdb?break (b) sets a persistent breakpoint that stops every time the line is reached. tbreak sets a temporary breakpoint that is removed automatically the first time it fires, which is ideal for stopping once inside a hot loop.

Can I add a condition to a breakpoint I already set? Yes. Use condition bpnumber expression to attach or change the condition on an existing breakpoint by its number, or condition bpnumber with no expression to clear it and make the breakpoint unconditional again.

← Back to Interactive Debugging with pdb and ipdb