Debugging Strategies Competent¶
🧠 Soft Skills · Level 2
When you'd use this
Systematic debugging, scientific method, rubber duck and common bug patterns.
Debug systematically — reproduce, isolate, hypothesize, verify — instead of changing things at random.
The scientific method for debugging¶
The scientific method for debugging — a key concept in Debugging Strategies.
1. OBSERVE — What exactly is the symptom?
2. HYPOTHESIZE — What could cause this?
3. PREDICT — If my hypothesis is right, what should happen when I...?
4. EXPERIMENT — Test the prediction
5. CONCLUDE — Was I right? If not, new hypothesis.
Strategy ladder (try in order)¶
Strategy ladder (try in order) in Debugging Strategies — what it is and when to use it.
| Step | Method | When |
|---|---|---|
| 1 | Read the error message | Always start here — Python errors are clear |
| 2 | Reproduce minimally | Strip away everything unrelated |
| 3 | Add print/logging | Quick check of values at key points |
| 4 | Use breakpoint() | Step through execution |
| 5 | Binary search | Comment out half the code — which half breaks? |
| 6 | Rubber duck | Explain the problem aloud (or to a duck) |
| 7 | Git bisect | Find which commit introduced the bug |
| 8 | Sleep on it | Fresh eyes see things tired eyes miss |
Common Python bug patterns¶
Common Python bug patterns in Debugging Strategies — what it is and when to use it.
# 1. Mutable default argument (classic trap)
def append_to(element, target=[]): # BAD — shared list!
target.append(element)
return target
# Fix:
def append_to(element, target=None):
if target is None:
target = []
target.append(element)
return target
# 2. Late binding in closures
functions = [lambda x: x + i for i in range(5)]
print([f(0) for f in functions]) # [4, 4, 4, 4, 4] — all use i=4!
# Fix: capture i as default argument
functions = [lambda x, i=i: x + i for i in range(5)]
print([f(0) for f in functions]) # [0, 1, 2, 3, 4]
# 3. Modifying list while iterating
items = [1, 2, 3, 4, 5]
for item in items:
if item % 2 == 0:
items.remove(item) # SKIPS elements!
# Fix: iterate over a copy or use comprehension
items = [x for x in items if x % 2 != 0]
# 4. == vs is
a = 1000
b = 1000
print(a == b) # True (same value)
print(a is b) # False! (different objects — outside cache range)
# 5. Forgetting to await
async def get_data():
return await fetch("...") # without await: returns coroutine, not data!
Git bisect — find the breaking commit¶
Git bisect — find the breaking commit, part of Debugging Strategies.
git bisect start
git bisect bad # current commit is broken
git bisect good abc1234 # this older commit was working
# Git checks out middle commit
# You test: does the bug exist?
git bisect good # or git bisect bad
# Repeat until: "abc5678 is the first bad commit"
git bisect reset
Practice Exercises¶
- Debug a provided broken function using only
printstatements (no debugger). - Use
breakpoint()to step through a recursive function and understand state at each level. - Find the bug in 5 tricky code snippets (mutable defaults, closures, off-by-one, etc.).
- Use
git bisectto find which commit broke a test. - Write a bug report for an open-source project — include reproduction steps, expected/actual behavior.
💬 Discussion
Have a question about this topic? Found an error? Share your thoughts below.