Debugging Python with pdb and breakpoint()
Learn how to debug Python code efficiently using pdb and the modern breakpoint() function, with practical examples and tips from real-world PythonSkillset projects.
Here's the article for PythonSkillset.com:
Debugging Python with pdb and breakpoint(): Your Code's Best Friend
Ever spent hours staring at your screen wondering why a variable turned into None or why your loop skipped every third element? I've been there too. Instead of adding ten thousand print() statements and hoping for the best, there's a better way. Let's talk about pdb and breakpoint().
Why I Stopped Using Print Statement Debugging
Back when I was building my first web scraper for PythonSkillset.com, I had a function that kept crashing halfway through parsing HTML. I added prints everywhere. The output was a mess of text mixed with actual data. It took me three hours to find one misplaced variable assignment. That's when I finally learned to debug properly.
Meet pdb: Python's Built-in Debugger
pdb stands for "Python Debugger." It's been in Python since version 2.1, and it's included with every Python installation. No pip install needed.
Basic usage starts with importing it:
import pdb
def calculate_average(numbers):
pdb.set_trace() # Execution stops here
total = sum(numbers)
avg = total / len(numbers)
return avg
result = calculate_average([10, 20, 30, 40])
When your code hits pdb.set_trace(), execution pauses, and you get an interactive prompt. Here are the commands you'll use most:
| Command | What It Does |
|---|---|
n (next) |
Execute current line, stop at next |
c (continue) |
Run until next breakpoint |
s (step) |
Step into a function call |
q (quit) |
Exit the debugger |
p variable_name |
Print the value of a variable |
l (list) |
Show code around current line |
The Modern Way: breakpoint() in Python 3.7+
Python 3.7 introduced breakpoint(), which is a cleaner alternative. It does exactly what pdb.set_trace() does, but with less typing:
def process_user_data(user_list):
breakpoint() # Same as pdb.set_trace()
active_users = [u for u in user_list if u['active']]
email_list = [u['email'] for u in active_users]
return email_list
Using breakpoint() has an extra advantage: you can disable all breakpoints globally by setting the PYTHONBREAKPOINT=0 environment variable. Your code runs without pausing even though breakpoint() calls remain.
A Real Debugging Session from PythonSkillset.com
Here's a scenario we actually ran into on our site. We had a function that should calculate the total time spent by users reading articles:
def calculate_reading_time(articles):
breakpoint() # Let's see what's happening
total_minutes = 0
for article in articles:
total_minutes += article['reading_time_minutes']
average = total_minutes / len(articles)
return average
# Data from our database
article_data = [
{'title': 'Python Decorators', 'reading_time_minutes': 8},
{'title': 'List Comprehensions', 'reading_time_minutes': 6},
{'title': 'Error Handling', 'reading_time_minutes': None} # Oops! Bug source
]
When breakpoint() fires, here's how you'd investigate:
- Type
p articlesto inspect the list — notice one hasreading_time_minutesasNone - Use
nto step through the loop, watchingtotal_minutes - When it hits the
Nonevalue, Python would crash with aTypeError - Type
p articleto see which item caused the error - Fix the issue: skip
Nonevalues or convert them to 0
Practical Tips for Everyday Debugging
Setting breakpoints manually works, but here's what I do for larger projects at PythonSkillset:
Conditional breakpoints: Want to stop only when a variable meets a condition?
for i in range(100):
if i == 42:
breakpoint() # Only pauses at iteration 42
process_data(i)
Debug in loops with minimal clutter: Instead of breaking inside a loop (which pauses each iteration), use
for i, item in enumerate(dataset):
if i % 50 == 0:
print(f"Processing item {i}") # Quick status, no break
if some_condition:
breakpoint() # Only when needed
Post-mortem debugging: When your script crashes, you can enter pdb after the error:
python -m pdb my_script.py
Then type continue to let it run. When it crashes, pdb stops at the error line, and you can inspect variables.
When breakpoint() Won't Cut It
For beginners, breakpoint() handles 90% of debugging needs. But if you're working with:
- Multi-threaded applications (threads confuse pdb)
- GUI applications (the pdb prompt blocks the interface)
- Very large data processing (stepping through billions of items is impractical)
...then you might need IDE debuggers (PyCharm, VS Code) or logging frameworks.
Common Mistakes I Still Make
Even after years of debugging, I still trip over these:
-
Forgetting to remove breakpoints in production: Always run code reviews checking for stray
breakpoint()calls. One time I pushed code to our live PythonSkillset.com server, and the entire site paused for a debugging session nobody wanted. -
Assuming breakpoint() works in all environments: On some CI/CD servers,
pdbinput isn't available. Your tests might hang forever. Use environment variables to conditionally enable breakpoints. -
Not knowing when to step vs. next: If you're inside a loop and call
son a function, you'll dive into that function's code. Sometimes you wantnto skip over the function entirely. Get comfortable with the difference; it saves time.
The Debugger Mindset
Debugging isn't about finding the problem — it's about understanding your code's actual behavior versus what you think it should do. Every time I hit a bug at PythonSkillset, I remind myself: the computer is always right. It's executing exactly what you told it to. The debugger shows you what that actually means.
Start with breakpoint(). Keep pdb commands handy. And for heaven's sake, stop printing everything. Your future self will thank you.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.