Skip to content

The Debugging Mindset: A Systematic Approach to Finding Bugs

August 05, 2024 · 3 min read

We've all been there: something's broken, you have no idea why, and you start making random changes hoping something will work. This is the path to madness (and confusing git histories).

Here's a better approach.

Step 1: Reproduce the Bug

If you can't reliably reproduce the bug, you can't fix it. You'll never know if your changes actually worked or if the bug just happens to be hiding.

Write down the exact steps to trigger the bug. Make them as minimal as possible:

1. Start the server
2. Navigate to /users/123
3. Click "Edit Profile"
4. Change email field
5. Click Save
→ Error: "Invalid token"

Step 2: Understand What Should Happen

Before you can fix what's wrong, you need to know what "right" looks like. What's the expected behavior? What's actually happening instead?

# Expected: Update user email and return success
# Actual: Throws "Invalid token" error

def update_user_email(user_id, new_email):
    # What should this do?
    # 1. Validate the email format
    # 2. Check if email is already in use
    # 3. Update database
    # 4. Return success response
    pass

Step 3: Form a Hypothesis

Based on the error message and the code, what's your best guess about what's going wrong?

"The 'Invalid token' error suggests the authentication middleware is rejecting the request. Maybe the token is expiring between page load and form submission?"

Step 4: Test Your Hypothesis

Don't just fix what you think is wrong. Verify your hypothesis first:

# Add logging to test hypothesis
def update_user_email(user_id, new_email):
    token = request.headers.get('Authorization')
    print(f"Token received: {token}")  # Debug log
    print(f"Token valid: {validate_token(token)}")  # Debug log

    # ... rest of function

If your hypothesis is wrong, you'll know immediately instead of chasing the wrong problem.

Step 5: Binary Search the Problem

If the codebase is large, narrow down where the bug lives:

  1. Add checkpoints: Put print statements or breakpoints at different stages
  2. Find the transition: Where does the data go from correct to incorrect?
  3. Zoom in: Focus on the code between the last good checkpoint and first bad one
function processData(input) {
    console.log('Input:', input);  // Checkpoint 1
    const cleaned = cleanData(input);
    console.log('Cleaned:', cleaned);  // Checkpoint 2
    const transformed = transform(cleaned);
    console.log('Transformed:', transformed);  // Checkpoint 3
    return validate(transformed);
}

Step 6: Fix One Thing at a Time

When you find the issue, resist the urge to fix multiple problems at once. Change one thing, test if it works, then move to the next issue.

This way, when something breaks (and it will), you know exactly which change caused it.

The Rubber Duck Method

When all else fails, explain the problem out loud to someone (or something—a rubber duck works great). Often, the act of articulating the problem reveals the solution.

"So when the user clicks save, it sends a POST request to /api/users/update, which calls the updateUser function, which... wait, why is it calling the old API endpoint? I updated the backend but forgot to update the frontend!"

Debugging Is a Skill

Good debugging isn't about being clever or knowing every quirk of your language. It's about being systematic, patient, and methodical.

The best debuggers I know don't have some magical intuition. They just have a process they follow every time, and they trust it to eventually lead them to the answer.

← Back to all posts