Debugging Like a Pro: Techniques Beyond print()

Sprinkling print() statements everywhere works for trivial bugs, but it doesn’t scale to a genuinely confusing problem — and it leaves a trail of debug output you have to remember to remove. A real debugger gets you to the answer faster with far less cleanup.

Using Python’s Built-in Debugger

def process_order(order):
    breakpoint()  # drops into pdb right here, no import needed (Python 3.7+)
    total = sum(item.price * item.qty for item in order.items)
    return apply_discount(total, order.customer.discount_rate)
n       next line (step over)
s       step into a function call
c       continue execution until next breakpoint
p total print the value of a variable
l       list surrounding source code
q       quit the debugger

Conditional Breakpoints: Stop Only When It Matters

Most debuggers (including VS Code’s) let you set a breakpoint that only triggers under a specific condition — invaluable when a bug only shows up on the 500th iteration of a loop:

for order in orders:
    # In VS Code: right-click the breakpoint, set condition to:
    # order.total < 0
    process_order(order)

Reading a Stack Trace Properly

Read from the bottom up — the last line is where the error actually occurred, and each frame above it shows the chain of calls that got you there. The first frame that’s in your code (not a library) is almost always where the real investigation should start.

Rubber Duck Debugging Is Real

Explaining your code line-by-line to literally anything — a colleague, a rubber duck, an empty text file — forces you to articulate assumptions you’ve been silently making, which is frequently where the actual bug turns out to be hiding.