The Debugging Framework That Finds Every Bug in Under 30 Minutes

Published: (February 19, 2026 at 05:16 PM EST)
6 min read
Source: Dev.to

Source: Dev.to

The Framework: REDUCE

Reproduce → Examine → Divide → Understand → Change → Evaluate

Step 1: Reproduce (5 minutes max)

If you can’t reproduce it, you can’t fix it. Period.

Goal: Find the minimal set of steps that triggers the bug.
Not “the user was doing stuff and it broke.”

1. Create a new user with email "test@test.com"
2. Add item to cart
3. Click checkout
4. Enter the same email "test@test.com" in billing
5. BUG: "Account already exists" error on payment page

If you can’t reproduce locally

Check environment differences. 90 % of “can’t reproduce” bugs are caused by:

  • Different data (production has edge‑cases local doesn’t)
  • Different timing (race conditions that require concurrent users)
  • Different configuration (env vars, feature flags, package versions)
  • Caching (local cache is empty, production cache has stale data)

Time‑box this step. If you can’t reproduce in 5 minutes, gather more data (logs, screenshots, user session) before trying more.

Step 2: Examine (3 minutes)

Before touching code, gather evidence. What do you KNOW (not suspect)?

FACTS:
- Error: "Account already exists" on POST /api/checkout
- Happens when billing email matches an existing user
- Started after deploy v2.3.1 (didn’t happen in v2.3.0)
- Error comes from UserService.findOrCreate()
- Only happens with email‑based lookup, not with userId

UNKNOWNS:
- Why does checkout call findOrCreate? (It shouldn't create users)
- What changed between v2.3.0 and v2.3.1?
- Is this related to the auth refactor merged last week?

Write this down—on paper, in a note, in a comment—anywhere physical. It prevents you from chasing your tail.

Step 3: Divide (The Key Step)

This is the step that makes the difference. Instead of reading all the code, binary‑search for the bug.

The request flow is: Router → Middleware → Controller → Service → Repository → Database.

Question: Is the bug in the first half or the second half?

// Add a log at the midpoint: the service layer
async checkout(data: CheckoutData) {
  console.log('SERVICE INPUT:', JSON.stringify(data));
  const user = await this.userService.findOrCreate(data.email);
  console.log('SERVICE USER:', JSON.stringify(user));
  // ... rest of checkout
}

Run the reproduction steps and check the logs.

  • If SERVICE INPUT has wrong data → bug is in the first half (router/middleware/controller).
  • If SERVICE INPUT is correct but SERVICE USER is wrong → bug is in findOrCreate.
  • If both are correct → bug is in the second half.

Now you’ve cut the search space in half. Repeat. Each division takes 1‑2 minutes. In 4‑5 divisions you’ve narrowed from the entire codebase to a single function.

This is git bisect but for code paths instead of commits.

Step 4: Understand (Don’t Just Fix the Symptom)

You’ve found the buggy function. Before changing it, understand WHY it’s wrong.

// The bug:
async findOrCreate(email: string): Promise {
  const user = await this.db.users.findOne({ email });
  if (!user) {
    return this.db.users.create({ email });  // BUG is HERE
  }
  return user;
}

The function finds an existing user or creates a new one. The checkout flow calls it to find the user. If the billing email differs from the account email, it tries to create a new user with the billing email—causing a conflict with the existing account.

Root cause: Checkout calls findOrCreate when it should call findByEmail. The findOrCreate behavior is correct for registration; it’s wrong for checkout.

If you’d just patched the symptom (e.g., a try‑catch around the create), you’d hide the architectural mistake and create more bugs later.

Step 5: Change (The Fix)

Fix the code to address the root cause, not the symptom.

// Fix: checkout should find, not create
async checkout(data: CheckoutData) {
  const user = await this.userService.findByEmail(data.billingEmail);
  if (!user) {
    throw new BadRequestError('No account found with this email');
  }
  // ... proceed with checkout
}

Step 6: Evaluate (Verify)

  1. Run the reproduction steps → bug is gone ✅
  2. Run the test suite → nothing else broke ✅
  3. Write a test for this specific bug
test('checkout with non‑matching billing email returns 400', async () => {
  const user = await createUser({ email: 'account@test.com' });
  const res = await api.post('/checkout', {
    ...validCheckoutData,
    billingEmail: 'different@test.com',
    userId: user.id,
  });
  expect(res.status).toBe(400);
  // Verify no new user was created
  const users = await db.users.findAll({ email: 'different@test.com' });
  expect(users).toHaveLength(0);
});

This test prevents the bug from ever coming back.

Advanced Techniques

Technique: The Printf Debugger (It’s Not Shameful)

console.log debugging gets a bad rap, but it’s the most practical tool when used systematically.

Don’t:

console.log('here');
console.log('here2');
console.log(thing);

Do:

console.log('[CHECKOUT] Input:', {
  email: data.email,
  itemsCount: data.items.length,
});

Add clear, contextual prefixes and log only the data you need. This keeps the output readable and makes it easy to spot anomalies.

Debugging Checklist & Techniques

1️⃣ Remove Debug Logging

console.log('[CHECKOUT] User lookup result:', { found: !!user, userId: user?.id });
console.log('[CHECKOUT] Payment attempt:', { amount, currency, method });

Action: When you’re finished, run the following command to delete all of these logs from the codebase:

grep -r "console.log.*\[CHECKOUT\]" -l | xargs sed -i '/console\.log.*\[CHECKOUT\]/d'

2️⃣ Technique: The Rubber Duck Explanation

Goal: Explain the bug in plain English.

“The checkout page shows ‘Account already exists’ when the billing email matches an existing user. The checkout flow calls findOrCreate, which tries to create a user when it shouldn’t. This started after we added billing email as a separate field in the checkout form.”

Half the time, simply verbalising the problem surfaces the solution.

3️⃣ Technique: The Diff Investigation

# What changed between the working version and the broken version?
git diff v2.3.0..v2.3.1 -- src/checkout/

# Who changed the checkout flow?
git log --oneline v2.3.0..v2.3.1 -- src/checkout/

# What did that specific commit do?
git show abc123

Tip: The bug was introduced by a specific change. Locate that change and you’ve found the root cause.

4️⃣ Technique: The Environment Swap

Problem: Bug only appears in production? Bring production to your local machine.

# Copy production data to local (sanitized!)
pg_dump production_db --exclude-table=secrets | psql local_db

# Replay production traffic locally
# (tools: GoReplay, mitmproxy)

# Compare configs
diff config/production.yml config/local.yml

Note: A brief conversation with a knowledgeable teammate can save hours of guesswork.

5️⃣ Community Prompt

What’s your go‑to debugging technique? I find the “Divide” step is where most people skip straight to reading all the code. Do you have a systematic approach, or are you a console.log warrior?

Comments welcome!

6️⃣ Bonus Resource

💡 AI Coding Prompts Pack – 50+ battle‑tested prompts, 6 Cursor rules files, and Claude Code workflow templates.
Grab it on Gumroad – $9 with lifetime updates.

0 views
Back to Blog

Related posts

Read more »