Table of Contents
What “Debugging” Really Means in Practice
Debugging is the process of finding and fixing the cause of a problem (a bug) in your code. In this chapter, you’ll focus on how to do that effectively.
You already know that errors happen and that Python shows error messages. Debugging strategies are about what you actually do once you notice something is wrong—whether it’s an error message or just wrong behavior.
This chapter is practical: step‑by‑step methods, habits, and tools you can use every day.
1. Start With a Clear Problem Statement
Before you dive into the code, be very clear:
- What is the program supposed to do?
- What is it actually doing?
- When exactly does it go wrong?
Write this down in plain language. For example:
- “The program should ask for two numbers and print their sum, but it crashes when I enter a decimal number.”
- “The loop should run 10 times but it runs forever.”
Having a clear “expected vs actual” description stops you from randomly changing code.
2. Reproduce the Bug Consistently
You can’t fix what you can’t reproduce.
- Try to find input that always causes the problem.
- Note the exact steps that lead to the bug.
Examples:
- “The program only crashes when the input is empty.”
- “The bug only appears when I type letters instead of numbers.”
Once you have steps to reliably trigger the bug, debugging becomes much easier.
3. Read Error Messages Systematically
Error messages were introduced earlier; here’s a simple strategy to use them:
- Look at the last line of the error.
- It tells you the error type (like
ValueError,IndexError). - Look at the line number and file name just above.
- Read the short description on the last line in plain language.
Example:
ValueError: invalid literal for int() with base 10: 'abc'Strategy:
- Error type:
ValueError→ wrong value type for an operation. - Description: Python tried to turn
'abc'into an integer, which is impossible. - Go to the line number mentioned and check where
int()is used.
Don’t just skim error messages; treat them as clues.
4. Add Temporary Print Statements
One of the simplest and most powerful debugging tools is print().
You can:
- Check the value of variables at different points.
- See which parts of code are actually being executed.
- Verify assumptions (“I think this variable is always positive”).
Example:
age = input("Enter your age: ")
print("DEBUG: raw age input =", age) # temporary debug print
age_num = int(age)
print("DEBUG: converted age =", age_num)Use these to answer:
- What is the variable right before the crash?
- Is the code taking the path you expect (inside
if, inside a loop, etc.)?
When you’re done, delete or comment out debugging prints.
5. Debugging by Simplifying (Narrowing Down)
If your program is long, don’t try to fix everything at once. Instead, isolate the problem.
Strategies:
- Comment out large sections of code.
- Run only a small part at a time.
- If the bug disappears, the problem is in the commented-out part.
- Replace complex code with a simpler version to test ideas.
Example: If a long function is failing:
def calculate_bill(items):
# Temporarily simplify to see if the problem is inside this function
print("DEBUG: calculate_bill called with:", items)
return 0 # placeholderIf the program runs without crashing now, the issue is inside your original function body.
6. Check One Change at a Time
When debugging, change only one thing and then run your code again.
- If you make many changes at once, you won’t know which one fixed (or broke) something.
- After each change:
- Run the code.
- See if the behavior changed.
- Keep or undo that change.
Think of it like a science experiment: one variable at a time.
7. Compare With a Smaller Example
If something is confusing, create a tiny, separate script that only tests the idea you’re unsure about.
Examples:
- Unsure how division works? Create a new file
test_division.py:
print(5 / 2)
print(5 // 2)- Confused about
iflogic? Make a small script with only thatifblock.
This “toy example” approach helps you understand behavior without distractions.
8. Trace the Code Step by Step (Mentally or on Paper)
Walk through the code line by line as if you were the computer.
For each line:
- What are the variable values before this line?
- What changes after this line?
- Which branch of
ifis taken? - Does the loop run again?
You can do this:
- In your head (for small code).
- On paper, writing variable names in a table:
Example:
Code:
x = 0
for i in range(3):
x = x + iTrace table:
| Step | i | x before | x after |
|---|---|---|---|
| 1 | 0 | 0 | 0 |
| 2 | 1 | 0 | 1 |
| 3 | 2 | 1 | 3 |
If the table doesn’t match what your program prints, you’ve found a misunderstanding.
9. Use the Python Debugger (`pdb`) (Gentle Intro)
For more control than print(), you can use the built-in debugger.
Basic use:
- Add this line where you want to pause execution:
import pdb; pdb.set_trace()- Run your script as usual.
- When Python hits that line, it stops and gives you a
(Pdb)prompt.
At the (Pdb) prompt, you can:
- Type a variable name to see its value:
x- Type
n(“next”) to run the next line. - Type
c(“continue”) to run until the next breakpoint or end.
You don’t need to master pdb now, but knowing it exists is useful for stepping through code line by line.
10. Check Inputs and Assumptions
Often, bugs happen because something you assumed is not true.
Ask questions like:
- “Can this variable be empty?”
- “Can this value be
0?” - “What happens if the user presses Enter without typing anything?”
- “What if the list is shorter than I expected?”
You can add checks:
if len(numbers) == 0:
print("DEBUG: numbers list is empty!")Or:
if age < 0:
print("DEBUG: negative age detected:", age)Debugging often means discovering that the real world doesn’t match your assumptions.
11. Use Assertions for Internal Checks
Assertions are a way to say “this must be true here; if not, crash with a clear message.”
Syntax:
assert condition, "Message if condition is False"Example:
total = sum(prices)
assert total >= 0, "Total price cannot be negative"
If the condition is False, Python raises an AssertionError with your message.
Assertions are especially useful to catch impossible or unexpected states earlier, closer to where they happen.
12. Binary Search Debugging (Divide and Conquer)
For bugs in longer code or data:
- Pick the middle point of the code or data.
- Add a
print()or check there. - See if things are correct up to that point.
- If it’s correct at the middle, the bug is in the second half.
- If it’s wrong at the middle, the bug is in the first half.
Repeat:
- Check the middle of the problematic half.
- Keep narrowing down until you find the exact line or operation.
This is like searching for a word in a dictionary by always opening in the middle.
13. Read the Code Out Loud (Rubber Duck Debugging)
Explaining your code, even to an object (like a “rubber duck”), often reveals mistakes.
Steps:
- Imagine someone knows basic Python but nothing about your code.
- Explain:
- “First, this function does X.”
- “Then it checks if Y.”
- “Here I expect Z to happen.”
As you explain, you may notice:
- “Wait, this condition can never be
True.” - “I’m saying this variable has the total, but it’s only the last item.”
You can also do this with a friend or classmate.
14. Use Version Control (Even in a Simple Way)
You don’t need advanced tools, but you should avoid losing working code.
Strategies:
- Make a backup file before big changes:
program_v1.py,program_v2.py- Or keep a copy of the last working version as:
program_working.py
Then:
- If a change breaks everything and you’re stuck, compare the new version with a working one.
- See exactly what you changed.
Later, you can learn git, but simple copying is already helpful for beginners.
15. Ask for Help Effectively
When you’re really stuck, asking for help is part of debugging.
When you ask, include:
- A short description of what you’re trying to do.
- What you expected to happen.
- What actually happens (including the full error message).
- A small code example that shows the problem (not your whole project if possible).
- What you already tried.
Example:
- “I tried converting user input to
int, but I getValueErrorwhen the input is'3.5'. I expected it to accept decimals. Here is the short code that shows the problem: …”
Being clear and specific speeds up getting a useful answer and also often helps you see the bug yourself while preparing the question.
16. Develop a Debugging Mindset
Good programmers are not people who never make mistakes; they are people who debug calmly and systematically.
Key attitudes:
- Don’t panic: Errors are normal.
- Be curious: “Why is this happening?” instead of “This is broken.”
- Be patient: Some bugs take several attempts to find.
- Be methodical: Use a process, not random changes.
Over time, you will:
- Learn to recognize patterns (“This looks like an off-by-one bug.”).
- Make fewer mistakes.
- Fix them faster when they do happen.
17. A Simple Debugging Checklist
When you face a bug, you can follow this simple checklist:
- Can I reproduce the bug every time?
- Did I read the full error message and check the line number?
- Did I print variable values around the problem area?
- Can I simplify or isolate the issue in a smaller script?
- Have I made only one change at a time and tested after each?
- Did I walk through the code step by step, maybe on paper?
- Have I checked my assumptions about inputs and data?
- If I’m still stuck, can I prepare a clear question with a small code example?
Using strategies like these will make debugging less frustrating and much more manageable as your programs grow in size and complexity.