| name | debugging-methodology |
| description | Scientific debugging methodology including hypothesis-driven debugging, bug reproduction, binary search debugging, stack trace analysis, logging strategies, and root cause analysis. Use when debugging errors, analyzing stack traces, investigating bugs, or troubleshooting performance issues. |
Debugging Methodology
This skill provides comprehensive guidance for systematically debugging issues using scientific methods and proven techniques.
Scientific Debugging Method
The Scientific Approach
1. Observe: Gather information about the bug
2. Hypothesize: Form theories about the cause
3. Test: Design experiments to test hypotheses
4. Analyze: Evaluate results
5. Conclude: Fix the bug or refine hypothesis
Example: Debugging a Login Issue
const packageLock = await fs.readFile('package-lock.json');
const testPassword = 'password123';
const oldHash = '$2b$10$...';
const newHash = await bcrypt.hash(testPassword, 10);
console.log(await bcrypt.compare(testPassword, oldHash));
console.log(await bcrypt.compare(testPassword, newHash));
Reproducing Bugs Consistently
Creating Minimal Reproduction
function handleSubmit() {
validateForm();
checkPermissions();
logAnalytics();
sendToServer();
updateUI();
showNotification();
}
function handleSubmit() {
sendToServer();
}
Reproducing Race Conditions
async function fetchUserData() {
const user = await fetchUser();
await new Promise(resolve => setTimeout(resolve, 100));
return user.profile;
}
Creating Test Cases
describe('Login', () => {
test('should authenticate user with valid credentials', async () => {
const user = await db.user.create({
email: 'test@example.com',
password: await bcrypt.hash('password123', 10),
});
const result = await login('test@example.com', 'password123');
expect(result.success).toBe(true);
expect(result.user.email).toBe('test@example.com');
});
});
Binary Search Debugging
Finding the Breaking Commit
git bisect start
git bisect bad
git bisect good v1.2.0
git bisect bad
git bisect good
git bisect reset
Automated Bisect
npm test 2>&1 | grep -q "Login test failed"
if [ $? -eq 0 ]; then
exit 1
else
exit 0
fi
git bisect start HEAD v1.2.0
git bisect run ./test.sh
Binary Search in Code
function processArray(arr: number[]): number {
}
function processArray(arr: number[]): number {
}
Stack Trace Analysis
Reading Stack Traces
Error: Cannot read property 'name' of undefined
at getUserName (/app/src/user.ts:42:20)
at formatUserProfile (/app/src/profile.ts:15:25)
at handleRequest (/app/src/api.ts:89:30)
at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
Analysis:
- Error type: TypeError - trying to access property on undefined
- Error message: "Cannot read property 'name' of undefined"
- Origin:
getUserName function at line 42
- Call chain: api.ts โ profile.ts โ user.ts
- Root cause location: user.ts:42
Investigating the Stack Trace
function getUserName(userId: string): string {
const user = cache.get(userId);
return user.name;
}
function getUserName(userId: string): string {
const user = cache.get(userId);
if (!user) {
throw new Error(`User not found in cache: ${userId}`);
}
return user.name;
}
Source Maps for Production
module.exports = {
devtool: 'source-map',
};
Logging Strategies
Strategic Log Placement
async function processOrder(order: Order) {
logger.info('Processing order', { orderId: order.id, items: order.items.length });
try {
logger.debug('Validating order', { orderId: order.id });
await validateOrder(order);
logger.debug('Processing payment', { orderId: order.id, amount: order.total });
const payment = await processPayment(order);
logger.info('Order processed successfully', {
orderId: order.id,
paymentId: payment.id,
duration: Date.now() - startTime,
});
return payment;
} catch (error) {
logger.error('Order processing failed', {
orderId: order.id,
error: error.message,
stack: error.stack,
});
throw error;
}
}
Structured Logging
import winston from 'winston';
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: {
service: 'order-service',
version: process.env.APP_VERSION,
},
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
Log Levels
logger.debug('Detailed debug information');
logger.info('Normal operation');
logger.warn('Warning but not an error');
logger.error('Error occurred');
logger.fatal('Critical failure');
const logLevel = {
development: 'debug',
staging: 'info',
production: 'warn',
}[process.env.NODE_ENV];
Debugging Tools
Using Debuggers
function calculateTotal(items: Item[]): number {
let total = 0;
for (const item of items) {
debugger;
total += item.price * item.quantity;
}
return total;
}
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Tests",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand"],
"console": "integratedTerminal"
}
]
}
Node.js Built-in Debugger
node --inspect index.js
node inspect index.js
> cont
> next
> step
> out
> repl
Memory Profiling
import v8 from 'v8';
import fs from 'fs';
function takeHeapSnapshot(filename: string) {
const snapshot = v8.writeHeapSnapshot(filename);
console.log(`Heap snapshot written to ${snapshot}`);
}
takeHeapSnapshot('before.heapsnapshot');
takeHeapSnapshot('after.heapsnapshot');
Performance Profiling
CPU Profiling
console.time('processLargeArray');
processLargeArray(data);
console.timeEnd('processLargeArray');
import { performance } from 'perf_hooks';
const start = performance.now();
processLargeArray(data);
const end = performance.now();
console.log(`Execution time: ${end - start}ms`);
Node.js Profiler
node --prof index.js
node --prof-process isolate-0x*.log > profile.txt
Chrome DevTools Performance
performance.mark('start-data-processing');
processData(data);
performance.mark('end-data-processing');
performance.measure(
'data-processing',
'start-data-processing',
'end-data-processing'
);
const measure = performance.getEntriesByName('data-processing')[0];
console.log(`Data processing took ${measure.duration}ms`);
Network Debugging
HTTP Request Logging
import axios from 'axios';
axios.interceptors.request.use(
(config) => {
console.log('Request:', {
method: config.method,
url: config.url,
headers: config.headers,
data: config.data,
});
return config;
},
(error) => {
console.error('Request error:', error);
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => {
console.log('Response:', {
status: response.status,
headers: response.headers,
data: response.data,
});
return response;
},
(error) => {
console.error('Response error:', {
status: error.response?.status,
data: error.response?.data,
message: error.message,
});
return Promise.reject(error);
}
);
Debugging CORS Issues
import cors from 'cors';
const corsOptions = {
origin: (origin, callback) => {
console.log('CORS request from origin:', origin);
const allowedOrigins = ['https://app.example.com'];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
console.log('CORS blocked:', origin);
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
};
app.use(cors(corsOptions));
Race Condition Detection
Using Promises Correctly
let userData = null;
async function loadUser() {
userData = await fetchUser();
}
async function displayUser() {
console.log(userData.name);
}
loadUser();
displayUser();
async function main() {
await loadUser();
await displayUser();
}
Detecting Concurrent Modifications
interface Document {
id: string;
content: string;
version: number;
}
async function updateDocument(doc: Document) {
const current = await db.document.findUnique({
where: { id: doc.id },
});
if (current.version !== doc.version) {
throw new Error('Document was modified by another user');
}
await db.document.update({
where: { id: doc.id, version: doc.version },
data: {
content: doc.content,
version: doc.version + 1,
},
});
}
Common Bug Patterns
Off-by-One Errors
const arr = [1, 2, 3, 4, 5];
for (let i = 0; i <= arr.length; i++) {
console.log(arr[i]);
}
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
Null/Undefined Issues
function getUserName(user: User): string {
return user.profile.name;
}
function getUserName(user: User | null): string {
if (!user) {
return 'Unknown';
}
if (!user.profile) {
return 'No profile';
}
return user.profile.name;
}
function getUserName(user: User | null): string {
return user?.profile?.name ?? 'Unknown';
}
Async/Await Pitfalls
async function getUser(id: string) {
const user = fetchUser(id);
return user.name;
}
async function getUser(id: string) {
const user = await fetchUser(id);
return user.name;
}
async function loadData() {
const users = await fetchUsers();
const posts = await fetchPosts();
const comments = await fetchComments();
}
async function loadData() {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments(),
]);
}
Root Cause Analysis (5 Whys)
The 5 Whys Technique
Problem: Application crashed in production
Why? Memory leak caused out-of-memory error
Why? Array of user sessions kept growing
Why? Sessions weren't being cleaned up
Why? Cleanup function wasn't being called
Why? Event listener for cleanup was never registered
Root Cause: Missing initialization code in new deployment script
RCA Template
## Root Cause Analysis
**Date**: 2025-10-16
**Incident**: API downtime (30 minutes)
### Timeline
- 10:00 - Deployment started
- 10:15 - First error reports
- 10:20 - Incident declared
- 10:25 - Rollback initiated
- 10:30 - Service restored
### Impact
- 500 users affected
- 10% of API requests failed
- $5,000 estimated revenue loss
### Root Cause
Database connection pool exhausted due to missing connection cleanup in new feature code.
### 5 Whys
1. Why did the API fail? โ Database connections exhausted
2. Why were connections exhausted? โ Connections not returned to pool
3. Why weren't connections returned? โ Missing finally block in new code
4. Why was the finally block missing? โ Code review missed it
5. Why did code review miss it? โ No automated check for connection cleanup
### Immediate Actions Taken
- Rolled back deployment
- Manually closed leaked connections
- Service restored
### Preventive Measures
1. Add linter rule to detect missing finally blocks
2. Add integration test for connection cleanup
3. Update code review checklist
4. Add monitoring for connection pool usage
### Lessons Learned
- Need better monitoring of connection pool metrics
- Database connection patterns should be abstracted
- Code review process needs improvement
Debugging Checklist
Before Debugging:
During Debugging:
After Fixing:
When to Use This Skill
Use this skill when:
- Debugging production issues
- Investigating bug reports
- Analyzing error logs
- Troubleshooting performance problems
- Finding memory leaks
- Resolving race conditions
- Conducting post-mortems
- Training team on debugging
- Improving debugging processes
- Setting up debugging tools
Remember: Debugging is detective work. Be systematic, stay curious, and always document what you learn. The bug you fix today will teach you how to prevent similar bugs tomorrow.