Best Practices and Tips — Debugging Techniques

Published: November 12, 2025 • Language: python • Chapter: 17 • Sub: 2 • Level: beginner

python

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:

  1. Observe the symptom → What is actually wrong? (crash, incorrect output, hang?)
  2. Reproduce the issue → Consistent reproduction is half the battle.
  3. Inspect and isolate → Narrow down the possible causes.
  4. Understand the root cause → Don’t fix the surface problem — fix why it happened.
  5. Apply a fix and test → Verify that the issue is resolved and nothing else broke.
  6. 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 line
  • s — step into function
  • c — continue execution
  • p variable — print variable value
  • q — 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 fatal
  • ERROR — an operation failed
  • CRITICAL — 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