Debug Detective
Systematic debugging: reproduce, isolate, fix, verify. Never guess. Always trace.
Debugging Process
1. REPRODUCE → Can you make it happen reliably?
2. OBSERVE → What exactly happens? (error message, logs, behavior)
3. ISOLATE → Where does it happen? (narrow down the code)
4. HYPOTHESIZE → What could cause this?
5. TEST → Verify hypothesis with minimal change
6. FIX → Apply the fix
7. VERIFY → Confirm fix works, no regressions
Step 1: Reproduce
Before touching any code, confirm you can reproduce the issue.
Questions to ask:
- What are the exact steps to trigger this?
- What input causes the failure?
- Does it happen every time or intermittently?
- What environment? (OS, browser, version)
- When did it start? What changed?
❌ WRONG: "Let me look at the code and guess"
✅ RIGHT: "Let me reproduce it first, then trace through the code"
Step 2: Read the Error
Error messages contain clues. Read them CAREFULLY.
Error: TypeError: Cannot read properties of undefined (reading 'name')
at UserService.getFullName (src/services/user.ts:42)
at UserController.profile (src/controllers/user.ts:15)
Clues:
- File: src/services/user.ts, line 42
- What: accessing .name on undefined
- Cause: something in the user object is undefined at that point
- Stack: called from UserController.profile at line 15
Action: Go to line 42, see what variable is undefined, trace back to where it should be set.
Common mistakes when reading errors:
❌ Ignoring the stack trace
❌ Reading only the error message, not the location
❌ Searching the error message on Google before understanding it
✅ Reading the full stack trace bottom-to-top
✅ Going to the exact file and line
✅ Understanding what variable is undefined/wrong
Step 3: Isolate
Binary search through the code to find the failure point.
Technique: Add Logging
def process_order(order):
print(f"DEBUG: order = {order}") # Is order valid?
items = order.get_items()
print(f"DEBUG: items = {items}") # Are items loaded?
total = calculate_total(items)
print(f"DEBUG: total = {total}") # Is total calculated?
charge = apply_discount(total, order.user)
print(f"DEBUG: charge = {charge}") # Where does it fail?
return charge
Technique: Minimal Reproduction
Strip away everything until you have the smallest case that fails.
// Original: Large React component with 200 lines
// ❌ Debugging the whole component
// ✅ Extract the failing logic
function calculateDiscount(price, user) {
const tier = user.membership.tier; // ← Is this where it fails?
const discount = DISCOUNTS[tier]; // ← Or here?
return price * discount;
}
// Test with minimal inputs:
calculateDiscount(100, { membership: { tier: "gold" } });
// Works? Problem is elsewhere. Fails? Found it.
Technique: Rubber Duck
Explain each line out loud. You'll often spot the bug while explaining.
"Okay, so we get the user from the database...
then we check if user.verified is true...
wait, verified is a string "true", not boolean true...
that's the bug!"
Step 4: Common Bug Patterns
Null/Undefined Access
// ❌ Crash: Cannot read property of undefined
const city = user.address.city;
// ✅ Safe access
const city = user?.address?.city ?? "Unknown";
# ❌ Crash: AttributeError
city = user.address.city
# ✅ Safe access
city = getattr(getattr(user, "address", None), "city", "Unknown")
Off-by-One Errors
# ❌ Misses last element
for i in range(len(items) - 1):
process(items[i])
# ✅ Processes all elements
for i in range(len(items)):
process(items[i])
# ✅✅ Even better
for item in items:
process(item)
Async/Timing Issues
// ❌ Race condition: map not populated yet
const cache = new Map();
loadData().then(data => cache.set("key", data));
const value = cache.get("key"); // undefined!
// ✅ Await properly
const cache = new Map();
const data = await loadData();
cache.set("key", data);
const value = cache.get("key");
State Mutation
# ❌ Modifying list while iterating
for item in items:
if item.score < 0:
items.remove(item) # Skips items!
# ✅ Iterate over copy or filter
items = [item for item in items if item.score >= 0]
Type Coercion (JavaScript)
// ❌ String comparison of numbers
"10" > "9" // false! (lexicographic)
"10" == 10 // true (coerced)
[] == false // true (coerced)
// ✅ Strict comparison
10 > 9 // true
"10" === 10 // false
Number("10") === 10 // true
Step 5: Tools
Console/Print Debugging
# Quick inspection
print(f"{variable=}") # Python 3.8+: shows name and value
print(f"{type(variable)=}") # Check type
pprint.pprint(complex_object) # Pretty print dicts/lists
// Quick inspection
console.log("variable:", JSON.stringify(variable, null, 2));
console.table(arrayOfObjects); // Tabular format
console.trace("who called this?"); // Stack trace
Debugger
# Python: breakpoint()
def process(data):
breakpoint() # Drops into pdb
result = transform(data)
return result
// JavaScript: debugger statement
function process(data) {
debugger; // Pauses in browser DevTools
const result = transform(data);
return result;
}
Git Bisect
# Find which commit broke something
git bisect start
git bisect bad HEAD # Current commit is broken
git bisect good v1.0.0 # This version worked
# Git checks out middle commit - test it
# If broken: git bisect bad
# If works: git bisect good
# Repeat until found
git bisect reset
Step 6: Verify the Fix
After fixing:
1. Reproduce the original bug → should be fixed
2. Run existing tests → no regressions
3. Test edge cases → the fix doesn't break related scenarios
4. Test the "opposite" case → normal flow still works
❌ Wrong: "I changed X and it seems to work"
✅ Right: "I changed X, confirmed original bug is fixed,
ran test suite (42 passed), tested edge case Y"
Debug Output Template
When reporting findings:
## Bug Report
**Symptom:** [What goes wrong]
**Root Cause:** [Why it goes wrong]
**Location:** [File:line]
**Trigger:** [What input/state causes it]
**Fix:** [What to change]
**Verification:** [How to confirm it's fixed]
**Regression Risk:** [What else might break]
Anti-Patterns
❌ Changing multiple things at once
(Which change fixed it? You'll never know.)
❌ Adding try/catch to silence errors
(The bug is still there, just hidden.)
❌ "It works on my machine"
(Check environment differences.)
❌ Copy-pasting Stack Overflow answers without understanding
(May fix this bug, introduce three more.)
❌ print("HERE1"), print("HERE2"), print("HERE3")
(Use descriptive debug messages.)