The Debugging Framework That Finds Every Bug in Under 30 Minutes
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 INPUThas wrong data → bug is in the first half (router/middleware/controller). - If
SERVICE INPUTis correct butSERVICE USERis wrong → bug is infindOrCreate. - 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 bisectbut 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)
- Run the reproduction steps → bug is gone ✅
- Run the test suite → nothing else broke ✅
- 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.logwarrior?
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.