Tutorial Overview
Series index: Superpowers Tutorial Series
systematic-debugging is Superpowers’ core debugging skill. It gives you a structured way to find the root cause systematically when a bug appears, instead of guessing at random.
What you will learn
- ✅ The four-phase debugging workflow
- ✅ Root-cause tracing techniques
- ✅ Defensive programming strategies
- ✅ Conditional waiting patterns
- ✅ Real-world debugging case studies
Why Do We Need Systematic Debugging?
Problems with traditional debugging
flowchart LR
A[Bug found] --> B[Guess the cause]
B --> C[Try a fix]
C --> D[Still broken]
D --> B
D --> E[Introduce a new bug]
style B fill:#ffcccc
style C fill:#ffcccc
style D fill:#ffcccc
The systematic debugging flow
flowchart TD
A[Bug found] --> B[Phase 1: Reproduce]
B --> C[Phase 2: Locate]
C --> D[Phase 3: Fix]
D --> E[Phase 4: Verify]
E --> F[Preventive actions]
style B fill:#e3f2fd
style C fill:#e8f5e9
style D fill:#fff3e0
style E fill:#f3e5f5
Effect comparison
| Metric | Guessing-based debugging | Systematic debugging |
|---|---|---|
| Average time to fix | 30-60 minutes | 10-20 minutes |
| First-pass fix rate | ~40% | ~85% |
| Introducing new bugs | Common | Rare |
| Recurrence | Frequent | Rare |
The Four-Phase Debugging Flow
Phase 1: Reproduce
Goal: Build a reliable reproduction path that triggers the bug consistently.
Key questions:
1. Under what conditions does the bug appear?
2. What input data is involved?
3. What is the expected behavior vs. the actual behavior?
4. Is the bug deterministic or intermittent?
Reproduction checklist:
- [ ] Can reproduce locally
- [ ] Clear reproduction steps
- [ ] Input data is known
- [ ] Expected output is known
- [ ] Actual output is known
- [ ] Reproduction can be minimized (irrelevant factors removed)
Example:
## Bug Reproduction Report
**Issue**: The tag list page crashes when the tag name contains special characters.
**Steps to reproduce**:
1. Visit /admin/tags/new
2. Enter the tag name "Test & Demo"
3. Click Save
4. Open the /tags page
**Expected**: Show all tags, including "Test & Demo"
**Actual**: Page returns a 500 error
**Error log**:
SyntaxError: Unexpected token ‘&’ in JSON at position 15
at JSON.parse (
**Environment**:
- Browser: Chrome 120
- Backend: Node.js 18.16
- Database: MySQL 8.0
**Reproduction rate**: 100% (5/5 runs)
Phase 2: Locate
Goal: Find the root cause of the bug, not just the surface symptom.
Localization techniques:
1. Binary search
# Problem: API response is slow
# Step 1: Determine whether the issue is in the frontend or backend
curl -w "%{time_total}\n" http://api/tags # backend response time
# Step 2: If it is backend-side, determine whether the slowdown is in the DB or business logic
# Add logs and measure separately:
# - Database query time
# - Business logic time
# - Serialization time
2. Log analysis
# Add debugging logs
import logging
logging.debug(f"Entering process_tag with name={tag_name}")
logging.debug(f"Tag name length: {len(tag_name)}")
logging.debug(f"Tag name bytes: {tag_name.encode()}")
try:
result = validate_tag(tag_name)
logging.debug(f"Validation result: {result}")
except Exception as e:
logging.error(f"Validation failed: {e}", exc_info=True)
raise
3. Breakpoint debugging
# Use pdb/ipdb
import pdb
def process_tag(tag_name):
pdb.set_trace() # Set a breakpoint
# Inspect variables in the interactive session
# (Pdb) tag_name
# (Pdb) type(tag_name)
# (Pdb) len(tag_name)
result = validate_tag(tag_name)
return result
4. Hypothesis validation
Hypothesis: the bug happens because special characters are not escaped.
Verification steps:
1. Test a tag without special characters -> works
2. Test a tag with special characters -> fails
3. Inspect the special-character handling code -> find the omission
4. Confirm the root cause: missing HTML entity escaping
Phase 3: Fix
Goal: Fix the root cause, not the symptom.
Fix principles:
1. Fix the root cause, not the symptom
2. Make the smallest possible change
3. Consider side effects
4. Add a regression test
Fix checklist:
- [ ] Root cause identified
- [ ] Solution designed (even if small)
- [ ] Impact scope assessed
- [ ] Reproduction test added
- [ ] Fix implemented
- [ ] All relevant tests run
Example fix:
// ❌ Fixing the symptom (not the cause)
function displayTagName(name) {
try {
return JSON.parse(name);
} catch (e) {
return name; // Ignore the error, the problem remains
}
}
// ✅ Fixing the root cause
function displayTagName(name) {
// Escape special characters when saving
return escapeHtml(name);
}
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
Phase 4: Verify
Goal: Confirm the fix works and does not introduce new problems.
Verification steps:
1. Run the reproduction test -> should pass
2. Run related tests -> all should pass
3. Manually verify -> behavior matches expectations
4. Run regression tests -> no unrelated breakage
5. Observe in production -> system remains healthy
Verification commands:
# 1. Run the reproduction test
pytest tests/test_tag_special_chars.py::test_tag_with_special_characters -v
# 2. Run related tests
pytest tests/test_tag*.py -v
# 3. Run the full test suite
pytest tests/ -v --tb=short
# 4. Check coverage
pytest tests/ --cov=app --cov-report=html
# 5. Manual verification
curl http://localhost:8000/api/tags | jq
Root Cause Tracing Techniques
Technique 1: 5 Whys
Keep asking “why” until you reach the root cause:
Problem: the tag page crashes
1. Why does it crash?
→ JSON parsing fails
2. Why does JSON parsing fail?
→ It contains special character &
3. Why was the special character not handled?
→ It was not escaped on save
4. Why was it not escaped?
→ The validation function only checks length, not characters
5. Why does the validation function not check characters?
→ The requirement analysis missed this edge case
Root cause: incomplete requirement analysis and missing input validation
Technique 2: Cause-and-effect diagram (Ishikawa/Fishbone)
Tag page crashes
│
┌────────────────┼────────────────┐
│ │ │
Code Data Environment
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ │ │ │ │ │
No escaping Missing validation Special chars Encoding issues Browser version Server config
Technique 3: Difference analysis
Compare normal vs. abnormal cases:
Normal case Abnormal case
-------- --------
Tag name: "Test" → Tag name: "Test & Demo"
No special chars Contains special char &
JSON parsing succeeds JSON parsing fails
Difference: special char &
Conclusion: special characters need to be escaped
Defensive Programming Strategies
Strategy 1: Input validation
# ❌ Do not trust the input
def create_tag(name):
tag = Tag(name=name) # Use it directly
tag.save()
# ✅ Defensive programming
def create_tag(name):
name = sanitize_input(name) # Clean input
validate_tag_name(name) # Validate
tag = Tag(name=name)
tag.save()
return tag
def sanitize_input(value):
if not isinstance(value, str):
raise TypeError("name must be a string")
return value.strip()
def validate_tag_name(name):
if not name:
raise ValidationError("name cannot be empty")
if len(name) > 50:
raise ValidationError("name too long")
if not re.match(r'^[a-zA-Z0-9 -]+$', name):
raise ValidationError("name contains invalid characters")
Strategy 2: Defensive logging
import logging
from contextlib import contextmanager
@contextmanager
def debug_context(operation):
logging.info(f"Starting: {operation}")
try:
yield
logging.info(f"Success: {operation}")
except Exception as e:
logging.error(f"Failed: {operation} - {e}", exc_info=True)
raise
# Usage
with debug_context("create_tag"):
tag = create_tag(name)
Strategy 3: Assertions
def process_tags(tag_ids):
# Preconditions
assert isinstance(tag_ids, list), "tag_ids must be a list"
assert len(tag_ids) > 0, "tag_ids cannot be empty"
tags = []
for tag_id in tag_ids:
tag = Tag.find(tag_id)
assert tag is not None, f"Tag {tag_id} not found"
tags.append(tag)
# Postconditions
assert len(tags) == len(tag_ids), "Not all tags found"
return tags
Conditional Waiting
Problem scenario
In asynchronous systems or concurrent environments, you often need to wait for a condition:
- Waiting for the database connection
- Waiting for file writes to complete
- Waiting for cache updates
- Waiting for an external API response
Wrong approaches
# ❌ Fixed sleep (unreliable)
import time
def wait_for_data():
time.sleep(5) # Wait 5 seconds
return get_data() # May still not be ready
# ❌ Busy waiting (wastes CPU)
def wait_for_data():
while not has_data():
pass # Spins forever
return get_data()
Correct approach
# ✅ Poll with timeout
import time
from typing import Callable, Any
def wait_for(
condition: Callable[[], bool],
timeout: float = 10.0,
interval: float = 0.1,
description: str = "condition"
) -> None:
"""
Wait until a condition becomes true, then raise on timeout
Args:
condition: Function used to check the condition
timeout: Timeout in seconds
interval: Polling interval in seconds
description: Human-readable description for errors
"""
start_time = time.time()
while time.time() - start_time < timeout:
if condition():
return
time.sleep(interval)
raise TimeoutError(f"Timeout waiting for {description}")
# Usage example
def wait_for_data():
wait_for(
lambda: has_data(),
timeout=10.0,
description="data to be available"
)
return get_data()
Waiting in async environments
# asyncio environment
import asyncio
async def wait_for_async(
condition: Callable[[], bool],
timeout: float = 10.0,
interval: float = 0.1,
description: str = "condition"
) -> None:
start_time = asyncio.get_event_loop().time()
while asyncio.get_event_loop().time() - start_time < timeout:
if condition():
return
await asyncio.sleep(interval)
raise asyncio.TimeoutError(f"Timeout waiting for {description}")
# Usage
async def wait_for_cache(key):
await wait_for_async(
lambda: cache.exists(key),
timeout=5.0,
description=f"cache key {key}"
)
return cache.get(key)
Debugging Checklist
Before debugging
- [ ] Understand the expected behavior
- [ ] Understand the actual behavior
- [ ] Collect error logs
- [ ] Collect environment information
During debugging
- [ ] Can reproduce reliably
- [ ] Minimize the reproduction steps
- [ ] Form hypotheses
- [ ] Validate hypotheses
- [ ] Find the root cause
After debugging
- [ ] Fix verified
- [ ] Tests pass
- [ ] Regression tests added
- [ ] Documentation updated
- [ ] Experience shared
Recommended Debugging Tools
Python debugging tools
# Code debugging
pip install ipdb # Enhanced pdb
# Performance analysis
pip install py-spy # Sampling profiler
pip install line_profiler # Line-level profiling
# Logging
pip install loguru # Better logging library
# Visual debugging
pip install viztracer # Trace visualization
JavaScript debugging tools
# Browser developer tools
# - Chrome DevTools
# - Firefox Developer Tools
# Node.js debugging
npm install -g ndb # Node.js debugger
# Performance analysis
node --inspect app.js # Debug with Chrome DevTools
General tools
# Network capture
wireshark
tcpdump
# System monitoring
htop
iotop
iftop
# Log analysis
jq # JSON processing
grep
awk
sed
Summary
systematic-debugging provides a structured debugging method:
- Reproduce - Build a reliable reproduction path
- Locate - Find the root cause
- Fix - Fix the root cause, not the symptom
- Verify - Confirm the fix works
Key takeaways
- ✅ Systematic, not guess-based
- ✅ Root cause, not symptom
- ✅ Verification, not assertion
- ✅ Prevention, not patching
Debugging quotes
“Debugging is the process of systematically removing errors, like scientific inquiry, not art.” — Brian Kernighan
“The hardest debugging is confirming that your assumption is wrong.” — Unknown
Series navigation:
- ← Previous: Tutorial 4: test-driven-development - TDD Practical Guide
- → Next: Tutorial 6: using-git-worktrees - Isolated Development Workflow
- Back: Series Index