Your cart is currently empty!
Debugging is an unavoidable part of software development. No matter how skilled you are, bugs will happen—and how efficiently you fix them can make a huge difference in your productivity and sanity.
Python offers powerful debugging tools that can help you track down issues quickly and effectively. However, most developers rely too much on print()
debugging, missing out on better debugging techniques that make troubleshooting easier.
This guide covers the best 7 Python debugging techniques to help you fix bugs faster, write cleaner code, and improve your workflow.
- Frequently Asked Questions About Python Debugging
- 1. Stop Using print(), Start Using pdb
- 2. Use Logging for Smarter Debugging
- 3. Debug Faster with IPython & Jupyter Notebook
- 4. Catch and Handle Exceptions Gracefully
- 5. Use pytest for Debugging Unit Tests
- 6. Improve Error Messages with better_exceptions
- 7. Track Memory Usage with tracemalloc
- Debug Smarter, Not Harder

Frequently Asked Questions About Python Debugging
Before diving into debugging techniques, let’s address some common debugging-related questions that developers often search for.
What is the best way to debug Python code?
The most effective way to debug Python code is by using Python’s built-in pdb
debugger or the breakpoint()
function (Python 3.7+). These tools allow you to pause execution and inspect variables interactively, making it easier to identify issues.
How do I debug a Python script without stopping execution?
Instead of using print()
statements, use the logging
module. It allows you to log debugging messages to a file or console without stopping the program, so you can analyze what’s happening while your script is still running.
Why is using print()
for debugging considered bad practice?
Relying on print()
for debugging is inefficient because:
- It clutters your code with unnecessary print statements.
- You have to rerun the script every time you make a change.
- It doesn’t provide real-time inspection of variable states.
Using tools likepdb
, logging, or an interactive debugger is a more scalable approach.
How do I debug Python code in Jupyter Notebook?
Jupyter Notebook has built-in debugging features. You can use the %debug
magic command after an error occurs to enter a post-mortem debugging session and inspect variable values.
How do I debug a failing test in Python?
Use pytest --pdb
when running tests with pytest
. This automatically opens an interactive debugger at the point where the test failed, letting you investigate the issue.
Debugging is about understanding how your code behaves and improving efficiency. Below are seven essential Python debugging techniques that will make you a faster, smarter, and more effective problem-solver.

1. Stop Using print()
, Start Using pdb
If you’re still relying on print()
statements to debug, you’re wasting time rerunning your script every time you change a print statement.
Instead, use Python’s built-in pdb
(Python Debugger), which lets you pause execution, inspect variables, and step through code interactively.
How to Use pdb
for Debugging
1️⃣ Insert the following line before the issue occurs:
pythonCopyEditimport pdb; pdb.set_trace()
or, in Python 3.7+, simply use:
pythonCopyEditbreakpoint()
2️⃣ Run your script as usual. Execution will pause at the breakpoint, and you can:
- Type
n
(next) to execute the next line. - Type
p variable_name
to print a variable’s value. - Type
c
(continue) to resume execution.
This allows real-time debugging without cluttering your code with print()
statements.
2. Use Logging for Smarter Debugging
print()
statements don’t scale well in large projects. If you’re debugging something complex, use Python’s logging module instead.
Why Logging is Better than print()
✅ Allows different log levels (INFO, DEBUG, WARNING, ERROR).
✅ Saves logs to a file for later analysis.
✅ Can be turned on/off easily without modifying code.
How to Implement Logging in Python
pythonCopyEditimport logging
logging.basicConfig(level=logging.DEBUG, filename="debug.log", filemode="w")
x = 10
logging.debug(f"Debugging: value of x is {x}")
logging.info("Everything is running smoothly.")
logging.warning("This is a warning message.")
Now, instead of spamming print()
, all debug messages go to a file, making them easier to track.

3. Debug Faster with IPython & Jupyter Notebook
If you’re working in data science, machine learning, or scripting, restarting your script every time you debug is inefficient.
Instead, use IPython’s interactive debugging tools or Jupyter Notebook’s %debug
magic command.
How to Debug in IPython
If an error occurs, type:
pythonCopyEdit%debug
This will open post-mortem debugging, allowing you to inspect variables without restarting the script.
How to Set Breakpoints in Jupyter
Insert this into a Jupyter cell:
pythonCopyEditfrom IPython.core.debugger import set_trace
set_trace()
This works just like pdb
but is optimized for Jupyter Notebook.
This method is game-changing for data scientists who need to debug scripts without losing their kernel state.

4. Catch and Handle Exceptions Gracefully
If your script crashes unexpectedly, it’s because uncaught exceptions terminate execution. Instead of letting that happen, use exception handling to debug smarter.
Example: Debugging with Try-Except
pythonCopyEdittry:
x = 1 / 0 # ZeroDivisionError
except ZeroDivisionError as e:
print(f"Error occurred: {e}")
✅ Why This Works:
- Prevents your script from crashing.
- Gives clear error messages.
- Can be combined with logging to track errors.
Using Exception Hooks for Global Debugging
If you want to catch all unhandled exceptions, use sys.excepthook
:
pythonCopyEditimport sys
def exception_handler(exc_type, exc_value, exc_traceback):
print(f"Unhandled exception: {exc_value}")
sys.excepthook = exception_handler
This will catch errors globally, making debugging easier in large applications.
5. Use pytest
for Debugging Unit Tests
If you’re writing tests with unittest
or pytest
, debugging failing tests can be annoying—especially when the output doesn’t tell you what went wrong.
With pytest
, you can drop into an interactive debugger the moment a test fails:
How to Debug Failing Tests with pytest
bashCopyEditpytest --pdb
This will pause execution where the test fails so you can inspect variables in real time.
Alternatively, inside your test, insert:
pythonCopyEditimport pytest
pytest.set_trace()
✅ Why This Helps:
- Debug exactly where the test fails instead of rerunning the whole suite.
- Speeds up troubleshooting for test-driven development.

6. Improve Error Messages with better_exceptions
Python’s default error messages aren’t always the easiest to read, especially when debugging complex issues. The better_exceptions
package enhances tracebacks, showing variable values at the time of failure.
How to Install better_exceptions
bashCopyEditpip install better_exceptions
How to Enable it in Your Script
pythonCopyEditimport better_exceptions
better_exceptions.hook()
✅ Why This Works:
- Shows actual variable values inside stack traces.
- More readable and informative than default Python tracebacks.
If you’re debugging large, complex applications, better_exceptions
makes error messages 10x more useful.
7. Track Memory Usage with tracemalloc
If your Python script uses more memory than expected or crashes due to high memory consumption, you might have a memory leak.
How to Detect Memory Leaks with tracemalloc
pythonCopyEditimport tracemalloc
tracemalloc.start()
# Run your function
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")
for stat in top_stats[:5]: # Show top 5 memory-consuming lines
print(stat)
✅ Why This Works:
- Shows which parts of your code consume the most memory.
- Helps detect inefficient object allocation.
This is especially useful for optimizing large datasets or long-running applications.

Debug Smarter, Not Harder
Debugging doesn’t have to be painful. By using the right Python debugging techniques, you can fix bugs faster, write more reliable code, and improve your development workflow.
✅ Use pdb
and breakpoint()
for interactive debugging.
✅ Replace print()
with logging
for structured debugging.
✅ Use %debug
and IPython.embed()
in Jupyter for real-time debugging.
✅ Enable better_exceptions
to improve error messages.
✅ Use tracemalloc
to find memory leaks.
🚀 Want to debug in style? Check out MadDosh’s Python-themed debugging mugs, T-shirts, and notebooks to rep your coder mindset!
Staff picks
-
$21.00
Koala Sunglasses Tee
-
$21.00
Sloth Tee
-
$21.00
Frog Sunglasses Tee
-
$21.00
Hedgehog Sunglasses Tee
-
$21.00
Dog Lover Graphic Tee – Labrador Retriever with Sunglasses Unisex T-Shirt
-
$21.00
Deer Sunglasses Tee
Debugging Circular Imports in Python: Clean Project Layout
Circular imports are one of the most common, annoying, and avoidable problems…
Debugging Python AsyncIO Errors: Event Loop Problems Solved
AsyncIO is deceptively simple—until it isn’t. You’re probably here because you hit…
How to Fix Python Memory Leaks With tracemalloc
Struggling with a Python app that keeps eating up memory? Learn how…
Fixing “ModuleNotFoundError” in Python (Fast Debugging Guide)
Struggling with Python’s dreaded ModuleNotFoundError? This fast debugging guide covers exactly why…
How to Resolve ImportErrors and ModuleNotFoundErrors in Python Projects
Struggling with Python import errors? Learn how to fix ImportError and ModuleNotFoundError…
Stop Writing Python Like JavaScript – Common Mistakes and How to Fix Them
Python and JavaScript are not the same, and yet, I keep seeing…
Leave a Reply