The Detective Analogy
When a crime occurs, detectives gather evidence, form hypotheses, and test them until they find the culprit. They don't just guess randomly.
Debugging is detective work for code.
When code misbehaves, you gather evidence (logs, error messages), form hypotheses (why it might fail), and test them (change code, observe results) until you find and fix the bug.
The Debugging Process
1. Reproduce → Can you make the bug happen consistently?
↓
2. Isolate → Where exactly is the problem?
↓
3. Identify → What's the root cause?
↓
4. Fix → Make the smallest change that fixes it
↓
5. Verify → Does it work? Any side effects?
Debugging Techniques
1. Print/Console Debugging
The simplest technique - add print statements:
function processOrder(order) {
console.log("Order received:", order);
const total = calculateTotal(order);
console.log("Total calculated:", total);
const tax = calculateTax(total);
console.log("Tax:", tax); // Bug! Tax is NaN
return total + tax;
}
2. Using a Debugger
Set breakpoints and step through code:
function findUser(users, id) {
debugger; // Execution pauses here
for (const user of users) {
if (user.id === id) {
// Inspect variables here
return user;
}
}
return null;
}
3. Rubber Duck Debugging
Explain the code out loud (to a rubber duck or colleague). The act of explaining often reveals the bug.
4. Binary Search Debugging
Comment out half the code. Does the bug still occur? Narrow down:
// Comment out half to isolate
// processPartA();
// processPartB();
processPartC();
// processPartD();
Browser DevTools
Console Panel
console.log("Message"); // Basic output
console.error("Error message"); // Red error
console.warn("Warning"); // Yellow warning
console.table([{ a: 1 }, { a: 2 }]); // Table format
console.trace("Stack trace"); // Call stack
console.time("timer"); // Start timer
// ... code ...
console.timeEnd("timer"); // End timer: <duration>
Sources Panel
- Set breakpoints (click line number)
- Step over (F10): Execute line, don't enter functions
- Step into (F11): Enter function calls
- Step out (Shift+F11): Exit current function
- Watch expressions: Monitor specific variables
Network Panel
Debug API calls:
- See request/response
- Check status codes
- Inspect headers and body
- Timing information
Common Bug Types
1. Off-by-One Errors
// Wrong - skips last element
for (let i = 0; i < arr.length - 1; i++) { ... }
// Correct
for (let i = 0; i < arr.length; i++) { ... }
2. Undefined/Null Reference
// Bug: user might be undefined
const name = user.name;
// Fix: check first
const name = user?.name || "Unknown";
3. Async Issues
// Bug: data is undefined
let data;
fetch("/api/data").then((r) => {
data = r.json();
});
console.log(data); // undefined - fetch hasn't completed!
// Fix: await or use in callback
const data = await fetch("/api/data").then((r) => r.json());
console.log(data);
4. Type Coercion
// Bug: string concatenation instead of addition
"5" + 3; // '53'
// Fix: parse to number
parseInt("5") + 3; // 8
Debugging Strategies
Read Error Messages Carefully
TypeError: Cannot read property 'name' of undefined
at getUserName (user.js:15)
at processUser (process.js:23)
This tells you:
- What: trying to access
nameonundefined - Where: line 15 of user.js
- How: called from processUser
Reproduce First
If you can't reproduce it, you can't fix it with confidence:
// Create a minimal test case
test("reproduces the bug", () => {
const input = {
/* exact input that causes the bug */
};
expect(() => processOrder(input)).toThrow();
});
Check Recent Changes
git log --oneline -10 # Recent commits
git diff HEAD~5 # What changed?
git bisect # Find which commit broke it
Common Mistakes in Debugging
Making Multiple Changes at Once
Bad: Change 5 things, test → works but don't know which fixed it
Good: Change 1 thing, test → know exactly what fixed it
Not Understanding Before Fixing
Patching symptoms without understanding the root cause leads to recurring bugs.
Assuming the Bug Is Elsewhere
"It works on my machine" → Check environment differences
"The library isn't behaving as expected" → Usually it's your code
"It was working yesterday" → Check what changed
FAQ
Q: When should I use a debugger vs console.log?
Console.log for quick checks. Debugger for complex state inspection, stepping through logic, or when you need to examine many variables.
Q: How do I debug production issues?
Logging is essential. Use structured logging, error tracking services (Sentry, LogRocket), and monitoring. Reproduce locally when possible.
Q: How long should I spend before asking for help?
Rule of thumb: some focused time. If stuck, explain the problem to someone - often you'll solve it while explaining.
Q: How do I debug memory leaks?
Use browser memory profiler or Node's --inspect flag. Take heap snapshots and compare what's accumulating.
Q: What if I can't reproduce the bug?
Add more logging. Check if it's environment-specific. Ask for more details from the reporter. Check for race conditions.
Q: How do I debug async code?
Add logging at each async boundary. Use async stack traces. Set breakpoints in then/catch handlers.
Summary
Debugging is a skill that improves with practice. Systematic approaches beat random guessing every time.
Key Points:
- Reproduce, isolate, identify, fix, verify
- Use console.log for quick debugging
- Use debugger for complex issues
- Read error messages carefully
- Make one change at a time
- Understand the cause, not just the symptom
- Check what changed recently
Every experienced developer spends significant time debugging. Getting good at it makes you more effective overall.
Leave a Comment
Comments (0)
Be the first to comment on this concept.
Comments are approved automatically.