Debugging is a critical skill for developers. This comprehensive guide covers systematic approaches and tools for finding and fixing bugs effectively.
Debugging Process
The Scientific Method
flowchart TD
A[Observe Problem] --> B[Form Hypothesis]
B --> C[Predict Outcome]
C --> D[Test Prediction]
D --> E{Matches Prediction?}
E -->|Yes| F[Confirm Fix]
E -->|No| G[Form New Hypothesis]
F --> H[Document Solution]
G --> A
Browser DevTools
Console Methods
// Basic logging
console.log('Value:', value);
console.info('Info message');
console.warn('Warning');
console.error('Error!');
// Styled logging
console.log('%cStyled text', 'color: blue; font-size: 14px');
// Table output
console.table([
{ name: 'John', age: 30 },
{ name: 'Jane', age: 25 }
]);
// Grouping
console.group('User Details');
console.log('Name:', user.name);
console.log('Email:', user.email);
console.groupEnd();
// Timing
console.time('fetchData');
await fetchData();
console.timeEnd('fetchData');
// Stack trace
console.trace('How did we get here?');
// Assertions
console.assert(user.isValid, 'User is invalid!');
Breakpoints
// In browser DevTools:
// 1. Set breakpoint in Sources panel
// 2. Or use debugger keyword
function processOrder(order) {
debugger; // Execution pauses here
// Check variables
const total = calculateTotal(order.items);
// Step through code
return applyDiscount(total);
}
// Conditional breakpoint
// Right-click breakpoint in DevTools
// Condition: user.id === null
Network Debugging
// Check API calls in Network tab
// Filter by: XHR, Fetch, WS
// Common issues to find:
// 1. Failed requests (red)
// 2. Wrong status codes
// 3. Slow responses
// 4. Wrong request/response format
// Copy as cURL
// Right-click request > Copy > Copy as cURL
Debugging JavaScript
Console Debugging
function findBug(data) {
console.log('Input:', data);
const result = data.items
.filter(item => item.active)
.map(item => item.value);
console.log('Filtered:', result);
const sum = result.reduce((a, b) => a + b, 0);
console.log('Sum:', sum);
return sum;
}
Using debugger
function debugFunction(data) {
const processed = data.map(item => {
debugger; // Pause on each iteration
return transform(item);
});
return processed;
}
Error Stack Traces
// Read stack traces carefully
Error: Cannot read property 'name' of undefined
at processUser (user.js:45)
at handleRequest (handler.js:12)
at app.get (app.js:30)
// Line numbers point to the bug:
// user.js:45 - where error occurred
// user.js:30 - called from here
// ...
Node.js Debugging
Built-in Debugger
# Start with debugger
node inspect app.js
# Commands in debugger:
# cont, c - Continue execution
# next, n - Step next
# step, s - Step in
# out, o - Step out
# repl - Evaluate code
Debug in VSCode
// .vscode/launch.json
{
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/app.js"
},
{
"type": "node",
"request": "attach",
"name": "Attach to Process",
"port": 9229
}
]
}
Node.js Logging
const pino = require('pino');
const logger = pino({
level: process.env.LOG_LEVEL || 'info'
});
logger.info('Application started');
logger.error({ err }, 'Failed to connect');
logger.debug({ query }, 'Database query');
Logging Strategies
Structured Logging
// Good structured logging
logger.info({
event: 'user_created',
userId: user.id,
email: user.email,
source: 'registration_form',
duration: Date.now() - startTime
}, 'User created successfully');
// Bad - hard to parse
console.log('User created: ' + user.id);
Log Levels
// Trace - detailed tracing
logger.trace({ fn: 'calculate' }, 'entering function');
// Debug - debugging info
logger.debug({ items: cart.length }, 'Processing cart');
// Info - important events
logger.info({ orderId: order.id }, 'Order placed');
// Warn - warning
logger.warn({ retry: attempt }, 'Retrying request');
// Error - errors
logger.error({ err, orderId: order.id }, 'Payment failed');
// Fatal - critical
logger.fatal({ err }, 'Database connection lost');
Common Bug Patterns
Null/Undefined
// Bug
const name = user.profile.name; // Crash if profile is null
// Fix - optional chaining
const name = user?.profile?.name;
// Fix - default value
const name = user?.profile?.name ?? 'Unknown';
Async Issues
// Bug - not awaiting
async function getData() {
fetch('/api/data'); // Forgot await!
return 'done'; // Wrong!
}
// Fix
async function getData() {
const result = await fetch('/api/data');
return result.json();
}
Scope Issues
// Bug - closure issue in loop
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // Prints 3, 3, 3!
}
// Fix - use let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // Prints 0, 1, 2
}
// Fix - use closure
for (var i = 0; i < 3; i++) {
((index) => {
setTimeout(() => console.log(index), 100);
})(i);
}
Debugging Production
Remote Debugging
# Enable debugging on remote server
node --inspect=0.0.0.0:9229 app.js
# Connect from local
chrome://inspect
# Click "Configure" and add remote server
Health Checks
// Add health check endpoint
app.get('/health', (req, res) => {
const healthcheck = {
uptime: process.uptime(),
memory: process.memoryUsage(),
cpu: process.cpuUsage(),
database: db.isConnected() ? 'ok' : 'error'
};
const status = healthcheck.database === 'ok' ? 200 : 503;
res.status(status).json(healthcheck);
});
Error Tracking
// Sentry integration
const Sentry = require('@sentry/node');
Sentry.init({ dsn: process.env.SENTRY_DSN });
// Capture errors
app.use(Sentry.Handlers.errorHandler());
// Manual capture
try {
riskyOperation();
} catch (err) {
Sentry.captureException(err);
}
Performance Debugging
Chrome Performance Tab
// 1. Open DevTools > Performance
// 2. Click Record
// 3. Perform actions
// 4. Stop and analyze
// Look for:
// - Long tasks (main thread blocking)
// - Large layout shifts
// - Expensive reflows
// - Memory leaks
Memory Leaks
// Check for memory leaks:
// 1. Performance > Memory > Take heap snapshot
// 2. Do action multiple times
// 3. Take another snapshot
// 4. Compare snapshots
// Common causes:
const cache = {};
function addToCache(key, value) {
cache[key] = value; // Growing unbounded!
}
// Fix with limits
const cache = new Map();
const MAX_SIZE = 100;
function addToCache(key, value) {
if (cache.size >= MAX_SIZE) {
cache.delete(cache.keys().next().value);
}
cache.set(key, value);
}
Testing to Debug
Writing Reproducing Tests
// Write a failing test that reproduces the bug
describe('Bug: cart total calculation', () => {
it('should handle empty cart', () => {
const cart = { items: [] };
expect(calculateTotal(cart)).toBe(0);
});
it('should apply discount correctly', () => {
const cart = {
items: [{ price: 100 }],
discount: 0.1
};
expect(calculateTotal(cart)).toBe(90);
});
it('should handle negative prices', () => {
const cart = {
items: [{ price: -10 }] // Bug: doesn't handle negative!
};
// This test will fail, revealing the bug
expect(calculateTotal(cart)).toBe(0);
});
});
Debugging Checklist
## Debugging Checklist
- [ ] Understand the error message
- [ ] Find the exact line causing the error
- [ ] Check input values at that point
- [ ] Look at function call stack
- [ ] Reproduce the issue consistently
- [ ] Check for race conditions
- [ ] Verify assumptions
- [ ] Test edge cases
- [ ] Add logging to trace values
- [ ] Write a test to reproduce
Tools
| Tool | Purpose |
|---|---|
| Chrome DevTools | Frontend debugging |
| VSCode Debugger | Step-through debugging |
| Node Inspector | Node.js debugging |
| Sentry | Error tracking |
| Datadog | APM |
| Wireshark | Network debugging |
| Postman | API testing |
External Resources
Conclusion
Effective debugging requires:
- Systematic approach
- Understanding of the codebase
- Proper tools
- Good logging
- Ability to reproduce issues
Practice makes perfect - the more you debug, the faster you become at finding bugs.
Key techniques:
- Use breakpoints strategically
- Add logging to trace values
- Write tests to reproduce issues
- Check assumptions
- Use error tracking tools
Comments