← Back to Overview · Part 2: Tools and Techniques →
| Read | Build | Watch | Test | Review | Visualize |
|---|---|---|---|---|---|
| You are here | Projects | — | — | Flashcards | — |
Debugging is the systematic process of finding and fixing bugs. It is not about staring at code until you see the problem — it is a repeatable method that works on any bug, in any language. The best debuggers are not the smartest programmers; they are the most methodical.
You will spend more time debugging than writing new code. A systematic approach turns frustrating hours of "why does this not work?" into a predictable process. The method described here — Reproduce, Isolate, Hypothesize, Test, Fix, Verify, Prevent — works for everything from a typo to a race condition.
Before fixing anything, you need to see the bug yourself. Write down the exact steps:
1. Run `python app.py`
2. Enter username "alice"
3. Enter password "test123"
4. Click "View Profile"
5. ERROR: KeyError: 'email'
If you cannot reproduce the bug, you cannot verify you fixed it. Ask: "Does this happen every time? Only with certain inputs? Only on certain machines?"
Remove variables until you find the smallest piece of code that still has the bug:
# The program is 500 lines. Where is the bug?
# Start by adding print statements at key points:
print("DEBUG: got to step 1")
print(f"DEBUG: user_data = {user_data}")
print("DEBUG: got to step 2")
# Or use the binary search method:
# 1. Add a print halfway through the code
# 2. Is the data correct at that point?
# - YES → bug is in the second half
# - NO → bug is in the first half
# 3. Repeat until you find the exact lineBased on the error and where it occurs, make a specific guess:
- "The
emailkey is missing because the API response changed format" - "The variable is None because the database query returned no results"
- "The loop runs one too many times because I used
<=instead of<"
Do NOT start changing code randomly. Have a theory first.
Test your theory with the smallest possible experiment:
# Hypothesis: user_data doesn't have an 'email' key
# Test: print the actual keys
print(f"DEBUG: keys = {user_data.keys()}")
# Hypothesis: the API is returning a different format
# Test: print the raw response
print(f"DEBUG: response = {response.json()}")If your hypothesis was wrong, go back to step 3 with new information.
Fix only the bug. Do not refactor surrounding code, add features, or "improve" things. Keep the change as small as possible:
# Before (buggy):
email = user_data["email"]
# After (fixed):
email = user_data.get("email", "no-email@example.com")Run the same reproduction steps from step 1. The bug should be gone. Also check that you did not break anything else — run the test suite.
Write a test that catches this specific bug:
def test_missing_email_field():
user_data = {"name": "Alice"} # No email key
result = process_user(user_data)
assert result.email == "no-email@example.com"Ask: "Why did this bug happen? Could it happen elsewhere? Is there a systematic fix?"
Usually means you passed the wrong type. Print the types of all arguments:
print(f"DEBUG: {type(x)=}, {type(y)=}")Print the actual keys/length before accessing:
print(f"DEBUG: keys = {data.keys()}")
print(f"DEBUG: len = {len(my_list)}")The object is not the type you think. Print what it actually is:
print(f"DEBUG: {type(obj)=}, {dir(obj)=}")Add assertions to check your assumptions:
assert len(results) > 0, "Expected results but got empty list"
assert isinstance(user, dict), f"Expected dict, got {type(user)}"| ← Overview | Part 2: Tools and Techniques → |
|---|