Section 02: Debugging Patterns
Quick debugging workflows and patterns using Claude Code for common development issues.
What You’ll Learn:
- Pattern-based debugging approach
- Common issue categories and solutions
- Effective debugging prompts
- Time-saving debugging workflows
Average time savings: 10-30x faster than traditional debugging
Tool/API/extension issues: Troubleshooting.
Table of Contents
Common Debugging Patterns
Pattern 1: Unhandled Async Errors
Symptom: API returns 500 errors intermittently with no logs
Root Cause: Promise rejections in async Express routes bypass error middleware
Solution Pattern:
// Add try-catch to async handlers
router.post('/endpoint', async (req, res, next) => {
try {
// Your code
} catch (error) {
next(error); // Pass to error middleware
}
});
// Or use express-async-errors globally
require('express-async-errors');
Claude Prompt:
git diff | claude "What promise rejections are unhandled? Suggest error handling."
Key Learning: Always wrap async Express handlers or use express-async-errors
Pattern 2: React Infinite Re-renders
Symptom: “Maximum update depth exceeded” error, browser freezes
Root Cause: useEffect depends on state it updates, creating infinite loop
Solution Pattern:
// ❌ Wrong: Effect updates user, depends on user
useEffect(() => {
fetchUser().then(data => setUser(data));
}, [user]); // Infinite loop!
// ✅ Correct: Don't depend on state you're updating
useEffect(() => {
fetchUser().then(data => setUser(data));
}, []); // Run once on mount
Claude Prompt:
claude "Why does this React component re-render infinitely?" --files="src/components/MyComponent.tsx"
Key Learning: Never put state in deps array if the effect updates that state
Pattern 3: TypeScript Type Recursion
Symptom: “Type instantiation is excessively deep and possibly infinite”
Root Cause: Circular generic types (A where B contains A
Solution Pattern:
// ❌ Wrong: Circular type reference
type ApiResponse<T> = {
data: T;
meta: { next?: ApiResponse<any> }; // Circular!
};
// ✅ Correct: Break circularity
type ApiResponse<T> = {
data: T;
meta: { nextUrl?: string }; // Use URL, not nested type
};
Claude Prompt:
npm run build 2>&1 | claude "Explain this TypeScript error in simple terms"
Key Learning: Avoid circular generic types; use IDs/URLs for relationships
Pattern 4: N+1 Query Problem
Symptom: API endpoint suddenly 40x slower after refactor
Root Cause: Loop making one database query per item instead of JOIN
Solution Pattern:
// ❌ Wrong: N+1 queries (1 + N separate queries)
const posts = await prisma.post.findMany();
for (const post of posts) {
post.author = await prisma.user.findUnique({ where: { id: post.authorId }});
}
// ✅ Correct: Single JOIN query
const posts = await prisma.post.findMany({
include: { author: true } // Single query with JOIN
});
Claude Prompt:
git diff main -- src/ | claude "What performance anti-patterns did this introduce?"
Full list: Anti-Patterns.
Key Learning: Always use include for relations; avoid loops with queries
Pattern 5: Flaky Tests in CI
Symptom: Test passes locally, fails randomly in CI (30% failure rate)
Root Cause: Using synchronous queries for async content
Solution Pattern:
// ❌ Wrong: getByText is synchronous
const element = screen.getByText('Loaded Data'); // Fails if not loaded yet
// ✅ Correct: findByText waits for async content
const element = await screen.findByText('Loaded Data'); // Waits up to 1000ms
Claude Prompt:
claude "Why is this test flaky in CI but passes locally?" --files="src/**/*.test.ts"
Key Learning: Use findBy* for async content, never setTimeout in tests
Pattern 6: Memory Leaks
Symptom: Memory grows from 100MB → 2GB over 24 hours, then crashes
Root Cause: Not removing items from Maps/Sets when connections close
Solution Pattern:
// ❌ Wrong: Never removes disconnected clients
const clients = new Map();
ws.on('connection', (ws, req) => {
clients.set(userId, ws);
ws.on('close', () => {
console.log('disconnected'); // But never removes from Map!
});
});
// ✅ Correct: Remove from Map on disconnect
ws.on('close', () => {
clients.delete(connectionId); // Clean up
});
Claude Prompt:
claude "What memory leaks exist? Check Maps, listeners, timers." --files="src/server.ts"
Key Learning: Always clean up Maps, event listeners, and timers
Pattern 7: CORS Issues in Production
Symptom: API works locally, fails in production with CORS error
Root Cause: Hardcoded localhost origin doesn’t match production domain
Solution Pattern:
// ❌ Wrong: Hardcoded localhost
app.use(cors({ origin: 'http://localhost:3000' }));
// ✅ Correct: Environment-based origins
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true
}));
Claude Prompt:
claude "CORS failing in production but works locally. Check configuration:" \
--files="backend/src/app.ts frontend/src/api/"
Key Learning: Use environment variables for origins, never hardcode
Pattern Application Guide
Step 1: Identify Pattern Category
| Symptoms | Pattern Category |
|---|---|
| 500 errors, no logs | Async error handling |
| UI freezes, max depth error | React re-renders |
| Build fails, type errors | TypeScript issues |
| Sudden slowness after change | Performance regression |
| Works locally, fails in CI | Environment differences |
| Memory grows over time | Memory leaks |
| Works locally, fails in prod | Configuration issues |
Step 2: Apply Claude Pattern
# Gather context
git diff [base] | claude "[Pattern-specific prompt]" --files="[relevant-files]"
# Claude identifies:
# - Root cause
# - Why it manifests this way
# - Specific fix with code
# - Related issues to check
# Apply fix
# Test fix
# Document learning
Step 3: Verify Solution
# For performance issues
git diff main | claude "Compare performance characteristics before/after"
# For functionality issues
npm test 2>&1 | claude "Verify fix addresses root cause"
# For production issues
claude "What could go wrong with this fix in production?" --files="[fixed-files]"
Quick Reference Prompts
By Issue Type
# Async/Promise issues
git diff | claude "What promise rejections are unhandled? Suggest error handling."
# Performance regression
git diff main -- src/ | claude "What performance anti-patterns did this introduce?"
# React issues
claude "Why does this component re-render infinitely?" --files="src/components/*.tsx"
# Type errors
npm run build 2>&1 | claude "Explain this TypeScript error and suggest fix"
# Test flakiness
claude "Why is this test flaky in CI?" --files="src/**/*.test.ts"
# Memory leaks
claude "What memory leaks exist? Check Maps, listeners, timers." --files="src/server.ts"
# CORS/Config issues
claude "Why does this work locally but fail in production?" \
--files="backend/ frontend/config/"
# General debugging
npm test 2>&1 | claude "Debug failures - explain root cause and fix"
By Workflow Stage
# Before committing (catch issues early)
git diff --cached | claude "Review for common bugs: async errors, N+1, memory leaks"
# After test failure
npm test 2>&1 | claude "Explain failures and suggest fixes"
# Performance investigation
claude "Profile this code for performance issues" --files="src/slow-endpoint.ts"
# Production incident
claude "Analyze this error log and suggest fix" < error.log
Debugging Workflow
Effective Debugging Process
graph TD
A[Issue Detected] --> B[Gather Context]
B --> C[Identify Pattern]
C --> D[Ask Claude]
D --> E[Understand Root Cause]
E --> F[Apply Fix]
F --> G[Test Fix]
G --> H{Works?}
H -->|Yes| I[Document Learning]
H -->|No| D
I --> J[Prevent Future Issues]
1. Gather Context (1-2 min)
# Collect relevant information
- Error messages
- Stack traces
- Recent changes: git diff main
- Relevant files
- Environment differences
2. Ask Claude (30 sec)
# Provide full context in one prompt
[error output] | claude "[Specific question about the pattern]" \
--files="[relevant-files]"
# Be specific:
# ✅ "What's the race condition causing this infinite loop?"
# ❌ "Fix my code"
3. Understand Root Cause (2-5 min)
- Read Claude’s explanation of why (not just what)
- Ask follow-up questions for clarity
- Verify the explanation matches your symptoms
4. Apply & Test Fix (5-10 min)
# Apply fix
# Test thoroughly
# Check for related issues Claude mentioned
5. Document Learning (1-2 min)
# Add to team knowledge base
# Update code review checklist
# Add preventive check to CI
Total time: 10-20 minutes (vs 2-4 hours traditional debugging)
Key Debugging Principles
1. Claude Explains “Why”, Not Just “What”
Traditional tools show what is broken. Claude explains:
- Why it’s broken
- How the bug manifests
- Why the fix works
- What else to check
2. Patterns Over Memorization
You don’t need to memorize every bug type. Recognize the pattern category, then ask Claude for specifics.
3. Iterative Refinement
Don’t expect one-shot solutions:
- Ask initial question
- Get analysis
- Ask follow-up for clarity
- Get deeper insight
- Apply understanding
4. Prevention > Cure
After fixing, ask Claude:
- “What else should I check?”
- “How do I prevent this?”
- “What monitoring should I add?”
5. Learn While Debugging
Each debugging session teaches concepts:
- Async error handling patterns
- React effect dependencies
- TypeScript type systems
- Database query optimization
- Testing best practices
- Memory management
- Configuration management
Time Savings Summary
| Pattern | Traditional | With Claude | Speedup |
|---|---|---|---|
| Async errors | 2-4 hours | 8 min | 15-30x |
| React re-renders | 1-2 hours | 5 min | 12-24x |
| Type recursion | 2-3 hours | 12 min | 10-15x |
| N+1 queries | 2-4 hours | 15 min | 8-16x |
| Flaky tests | 3-5 hours | 10 min | 18-30x |
| Memory leaks | 4-8 hours | 25 min | 10-20x |
| CORS issues | 1-3 hours | 7 min | 9-26x |
| Average | 2-4 hours | 12 min | 10-30x |
Cost: $0.03-0.12 per debugging session
ROI: Even at 1 bug per week: 2-4 hours saved = $200-400 value for $0.50 cost
When to Use Claude vs Traditional Tools
Use Claude When:
- ✅ Need to understand “why”
- ✅ Cryptic error messages
- ✅ Pattern recognition (seen this before?)
- ✅ Multiple possible causes
- ✅ Need to learn while fixing
Use Traditional Tools When:
- ✅ Need exact execution trace
- ✅ Performance profiling with metrics
- ✅ Memory heap analysis
- ✅ Network request inspection
- ✅ Step-by-step debugging
Best Approach: Combine Both
- Use profiler/debugger to identify what is slow/broken
- Use Claude to understand why and how to fix
- Use traditional tools to verify fix
Summary
Key Takeaways:
- Recognize patterns - Most bugs fall into 7 common categories
- Ask specific questions - “What’s the race condition?” not “Fix my code”
- Understand root causes - Learn why, not just what
- Apply iteratively - Follow up for clarity
- Prevent recurrence - Ask about related issues
Expected Results:
- 10-30x faster debugging
- Deeper understanding of root causes
- Prevention of similar future bugs
- Team learning from each session
Next Steps:
- Bookmark quick reference prompts
- Try pattern-based approach on next bug
- Share learnings with team
- Build team debugging prompt library