Skip to main content

🔍 Debugging

Detective work to find code bugs

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 name on undefined
  • 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.

Related Concepts

Leave a Comment

Comments (0)

Be the first to comment on this concept.

Comments are approved automatically.