ch17s2_DebuggingTechniques
Debugging is more than fixing bugs — it’s the art of **understanding what your code is actually doing**, not just what you *think* it’s doing.
Chapter 17: Best Practices and Tips — Debugging Techniques
🧠 Introduction: The Art and Science of Debugging
Debugging is more than fixing bugs — it’s the art of understanding what your code is actually doing, not just what you think it’s doing.
It blends systematic investigation, curiosity, and precision thinking to uncover and resolve issues efficiently.
“If debugging is the process of removing bugs, then programming must be the process of putting them in.” — Edsger Dijkstra
Debugging well saves time, improves software reliability, and strengthens your mastery of the codebase.
🧩 1. Understanding the Debugging Workflow
Before touching any code, slow down and think. Great debugging follows a structured process:
- Observe the symptom → What is actually wrong? (crash, incorrect output, hang?)
- Reproduce the issue → Consistent reproduction is half the battle.
- Inspect and isolate → Narrow down the possible causes.
- Understand the root cause → Don’t fix the surface problem — fix why it happened.
- Apply a fix and test → Verify that the issue is resolved and nothing else broke.
- Prevent recurrence → Add tests, assertions, or logging to catch similar issues early.
🔍 2. Observational Debugging: The Basics
🖨️ Using Print Statements
Simple print() debugging can be surprisingly effective for quick insights.
def calculate_average(numbers):
total = sum(numbers)
count = len(numbers)
average = total / count
print(f"[DEBUG] total={total}, count={count}, average={average}")
return average
Pro Tip: Prefix prints with tags like [DEBUG], [INFO], or [STATE] to locate them easily.
🧱 Assertions: Guard Your Assumptions
Assertions detect logic errors early by verifying that certain conditions are always true.
def divide(a, b):
assert b != 0, "Division by zero!"
return a / b
Assertions are for development, not user error handling — disable them in production with
python -O.
🧭 3. Interactive Debugging: Step Through Code
🐞 Using breakpoint() (Built‑in Debugger)
Python’s built‑in debugger, pdb, allows you to pause code execution and inspect variables.
def divide(a, b):
breakpoint() # pauses execution here
return a / b
At the (Pdb) prompt, you can:
n— step to next lines— step into functionc— continue executionp variable— print variable valueq— quit debugging
Enhanced Debuggers
- ipdb → better color and formatting (
pip install ipdb) - VS Code / PyCharm → graphical debuggers with variable inspection and watches
Use breakpoints instead of scattershot
print()— it’s faster and cleaner.
🧾 4. Analytical Debugging: Logging and Tracing
🧩 Using the logging Module
logging lets you record information during runtime without cluttering output.
import logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%H:%M:%S"
)
def calculate_square_root(number):
logging.debug(f"Calculating square root of {number}")
result = number ** 0.5
logging.info(f"Result = {result}")
return result
Common Levels:
DEBUG— detailed internal info (during development)INFO— general events (program flow)WARNING— something unexpected but not fatalERROR— an operation failedCRITICAL— serious error, program may stop
🧩 Using trace and cProfile
When you need to analyze execution flow or performance bottlenecks:
python -m trace --trace myscript.py
python -m cProfile myscript.py
These show which functions are called and how long they take.
Profiling is debugging for performance.
🧠 5. Rubber Duck Debugging
Explaining your code to someone else (or even a rubber duck 🦆) forces you to articulate your logic.
You’ll often spot mistakes as you verbalize them.
“If you can’t explain what your code does in simple terms, you probably don’t understand it yet.”
🧩 6. Debugging Common Issues
| Issue Type | Example | Debugging Strategy |
|---|---|---|
| Syntax Error | SyntaxError: invalid syntax | Check for missing colons, quotes, or parentheses |
| Logic Error | Wrong output | Add print/logs for intermediate values |
| Runtime Error | ZeroDivisionError, KeyError | Inspect input data and edge cases |
| Performance Lag | Code runs too slow | Use cProfile or timeit |
| State Corruption | Unexpected global changes | Track variable scopes and mutability |
🧩 7. Debugging Mindset: Root Cause Thinking
Debugging isn’t guessing — it’s detective work.
- Don’t fix the symptom; fix the cause
- Don’t say “it works now” until you know why
- Don’t skip writing tests after fixing a bug
- Don’t ignore warnings — they often reveal deeper issues
- Keep notes on your debugging journey; patterns repeat!
Every bug has a story. Learn to read it, not just erase it.
🧰 8. Debugging Tools to Know
| Tool | Purpose |
|---|---|
| pdb / breakpoint() | Interactive command-line debugger |
| ipdb | Colored, enhanced version of pdb |
| VS Code / PyCharm Debugger | Visual stepping and inspection |
| logging | Structured runtime output |
| trace | Function call tracing |
| cProfile / line_profiler | Performance profiling |
| pytest —pdb | Drop into debugger when a test fails |
🧩 9. Common Debugging Anti‑Patterns
| Anti‑Pattern | Why It’s Dangerous |
|---|---|
| Random code edits (“shotgun debugging”) | May fix symptoms, not causes |
| Ignoring reproducibility | Hard to verify real fix |
| Adding endless prints | Creates noise and hides root cause |
| Not testing after fix | Risk of regression |
| Blaming libraries | 90% of “library bugs” are in user code 😄 |
✅ 10. Debugging Checklist
- Can you reproduce the issue consistently?
- What changed before the bug appeared?
- Have you isolated the smallest possible failing case?
- Have you read the full traceback?
- Have you verified all variable values?
- Did you test your fix thoroughly?
- Did you add logging or assertions to prevent reoccurrence?
🧭 Conclusion
Debugging is not just a rescue skill — it’s a superpower that sharpens your logical reasoning and understanding of systems.
By using tools like print debugging, logging, breakpoints, and profilers — and by cultivating a detective mindset — you’ll not only fix bugs faster but also write more reliable software.
“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are — by definition — not smart enough to debug it.” — Brian Kernighan