Back

Superpowers Tutorial 5: systematic-debugging (Root Cause Analysis Workflow)

systematic-debugging is a Superpowers skill for root-cause analysis. It enforces a four-phase workflow (reproduce → hypothesize → validate → fix) so you stop guessing and start proving. This guide shows what evidence to collect and how to prevent regressions.

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 () at TagList.js:45


**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:

# 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 = {
        '&': '&',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#039;'
    };
    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

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:

  1. Reproduce - Build a reliable reproduction path
  2. Locate - Find the root cause
  3. Fix - Fix the root cause, not the symptom
  4. 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:

References

Last updated on Mar 26, 2026 00:00 UTC