DEV Community

Alex Chen
Alex Chen

Posted on

Debugging JavaScript: The Guide I Wish I Had Earlier (2026)

Debugging JavaScript: The Guide I Wish I Had Earlier (2026)

Debugging is a skill. A learnable skill. Here's how to find and fix bugs faster.

The Mindset

Before touching any tool:
1. Reproduce the bug consistently
   → Can you make it happen every time?
   → What are the EXACT steps?
   → What's different when it works vs when it doesn't?

2. Form a hypothesis
   → "I think it's because X is undefined here"
   → "I think the race condition happens between step 3 and 4"
   → Write down your hypothesis BEFORE investigating

3. Prove yourself wrong
   → The goal is NOT to confirm your guess
   → The goal is to DISPROVE it as fast as possible
   → If you can't disprove it, you're probably right

4. Fix the ROOT cause, not the symptom
   → Adding a try/catch = hiding the problem
   → Adding a setTimeout = racing with the bug, not fixing it
   → Understand WHY before fixing HOW
Enter fullscreen mode Exit fullscreen mode

Console Techniques You Need

// 1. console.table — beautiful data display
const users = [
  { id: 1, name: 'Alice', role: 'admin' },
  { id: 2, name: 'Bob', role: 'user' },
  { id: 3, name: 'Charlie', role: 'moderator' },
];
console.table(users);
// Outputs a formatted table — way better than console.log!

// Filter columns:
console.table(users, ['name', 'role']);

// 2. console.group — organize related logs
function processOrder(order) {
  console.group(`📦 Order ${order.id}`);
  console.log('Customer:', order.customer);
  console.log('Items:', order.items.length);

  console.group('💰 Pricing');
  console.log('Subtotal:', order.subtotal);
  console.log('Tax:', order.tax);
  console.log('Total:', order.total);
  console.groupEnd();

  console.groupEnd();
}

// 3. console.time / timeLog / timeEnd — measure performance
console.time('fetch-data');
fetch('/api/data')
  .then(r => r.json())
  .then(data => {
    console.timeLog('fetch-data', 'received response'); // Intermediate timing
    processData(data);
    console.timeEnd('fetch-data'); // Final: "fetch-data: 234ms"
  });

// 4. console.assert — silent unless condition fails
function validateUser(user) {
  console.assert(user.email.includes('@'), 'Invalid email:', user.email);
  console.assert(user.age >= 18, 'Underage user:', user.age);
  // Only logs if assertion FAILS
}

// 5. console.trace — see the call stack
function updateConfig(key, value) {
  if (key === 'dangerousSetting') {
    console.trace('Dangerous setting changed!'); // Shows full call stack!
  }
  config[key] = value;
}

// 6. console.count — count how many times something runs
function handleRequest(req) {
  console.count('requests'); // "requests: 1", "requests: 2", ...
  // Reset: console.countReset('requests')
}

// 7. CSS styling in console
console.log(
  '%c ERROR %c Something went wrong!',
  'background: red; color: white; font-weight: bold; padding: 2px 8px;',
  'color: red;'
);

// 8. String substitution (like printf!)
const user = { name: 'Alice', score: 95 };
console.log('User: %o scored %d points!', user, user.score);
// %s = string, %d/%i = number, %o = object, %O = expandable object, %f = float, %c = style
Enter fullscreen mode Exit fullscreen mode

Breakpoints & Source Maps

// 1. debugger statement (the simplest breakpoint!)
function complexCalculation(a, b) {
  const sum = a + b;
  debugger; // Pauses execution HERE in DevTools
  return sum * 2;
}
// Open DevTools → code pauses at this line → inspect all variables!

// 2. Conditional breakpoints (in DevTools Sources panel)
// Right-click line number → "Add conditional breakpoint"
// Enter: items.length > 1000
// Only pauses when condition is true (great for loops!)

// 3. Logpoint (non-breaking log)
// Right-click line number → "Add logpoint"
// Enter: 'Processing item:', itemName
// Logs without pausing execution!

// 4. XHR/Fetch breakpoints
// DevTools → Sources → XHR Breakpoints
// Add URL fragment: "/api/users"
// Pauses on ANY request matching that string

// 5. DOM breakpoint
// Elements panel → Right-click element → Break on:
//   • Subtree modifications (child added/removed)
//   • Attribute modifications (class, style changes)
//   • Node removal (element deleted)

// 6. Event listener breakpoint
// DevTools → Sources → Event Listener Breakpoints
// Check "click" → pauses on ANY click event
// Great for finding where click handlers are registered!
Enter fullscreen mode Exit fullscreen mode

Network Debugging

// 1. Copy as cURL (DevTools Network tab)
// Right-click request → Copy → Copy as cURL
// Paste in terminal to reproduce request exactly
// Modify and re-run to test API behavior

// 2. Override responses (DevTools Network panel overrides)
// Sources → Overrides → Enable local overrides
// Right-click network response → Save for overrides
// Edit the saved file → browser uses YOUR version instead!

// 3. Throttle network speed
// DevTools → Network → No throttling dropdown:
//   Slow 3G: 500 Kbps upload, 50ms RTT (test real mobile experience!)
//   Fast 3G: 1.5 Mbps upload, 150ms RTT
//   Custom: Set exact speeds

// 4. Block specific requests
// DevTools → Network → Request blocking → Enable
// Add pattern: *.ads.js → blocks all ad scripts
// Test how your app behaves without third-party resources

// 5. Service Worker debugging
// Application → Service Workers
// Check "Update on reload" for development
// "Push" button to simulate push notifications
// "Sync" to test background sync
Enter fullscreen mode Exit fullscreen mode

Memory Debugging

// 1. Heap snapshot (find memory leaks!)
// DevTools → Memory → Take heap snapshot
// Do operation that might leak → Take another snapshot
// Compare: Objects created but not freed = leak!

// 2. Allocation sampling (lightweight profiling)
// DevTools → Memory → Allocation sampling
// Run for 30 seconds while using app
// See which functions allocate most memory

// 3. Performance monitor (real-time metrics)
// DevTools → Rendering → Performance Monitor (Esc key)
// Watch: CPU usage, JS heap size, DOM nodes, event listeners
// If JS heap keeps growing → memory leak!

// Common memory leaks in JavaScript:

// Leak 1: Forgotten timers/intervals
// ❌ Component unmounts but interval keeps running
componentDidMount() {
  this.timer = setInterval(() => this.updateData(), 1000);
}
// ✅ Cleanup on unmount
componentWillUnmount() {
  clearInterval(this.timer);
}

// Leak 2: Event listeners not removed
// ❌
window.addEventListener('resize', this.handleResize);
// ✅
window.addEventListener('resize', this.handleResize);
// Later: window.removeEventListener('resize', this.handleResize);

// Leak 3: Closures holding references
function createHandler(element) {
  let hugeData = new Array(1000000).fill('data');
  return function handler() {
    console.log('clicked'); // hugeData stays in memory forever!
  };
}
// element.onclick = createHandler(el); // hugeData never GC'd!

// Leak 4: Detached DOM nodes
const list = document.getElementById('list');
list.innerHTML = ''; // Removes children from DOM...
// But if you hold references to them in JS, they can't be GC'd!
Enter fullscreen mode Exit fullscreen mode

Common Bugs & How to Find Them

// Bug 1: "Cannot read property X of undefined"
// Cause: Chaining on possibly-undefined values
// Find: Look at the EXACT line in stack trace
// Fix: Optional chaining or guard clause

const userName = user?.profile?.name ?? 'Anonymous';
// or:
if (!user || !user.profile) return;
const name = user.profile.name;

// Bug 2: "X is not a function"
// Cause: Variable holds unexpected type
// Find: console.log(typeof variable, variable)
// Common: Imported wrong thing, async function result, property name typo

// Bug 3: Race conditions
// Symptom: Works sometimes, fails other times
// Find: Add timestamps to all async operations, look for ordering issues
// Fix: Proper sequencing (await chain), mutex/lock, or deduplication

// Bug 4: State mutation in React
// Symptom: UI doesn't update after data change
// Find: React DevTools → Components → check props/state
// Fix: Create NEW objects/arrays (never mutate directly!)

// Bug 5: Off-by-one errors
// Symptom: Missing first/last item, index out of bounds
// Find: Log array length and indices being accessed
// Use: Array.from({length: n}, (_, i) => i) to verify bounds

// Bug 6: Timezone/date bugs
// Symptom: Wrong date displayed, comparisons fail
// Find: console.log(date, date.toISOString(), date.getTime())
// Fix: Always use UTC internally, format for display only
const now = new Date();
const utcDate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
Enter fullscreen mode Exit fullscreen mode

Production Debugging

// 1. Error boundaries (React)
class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, info) {
    logErrorToService(error, info.componentStack); // Send to error tracking!
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>; // Fallback UI
    }
    return this.props.children;
  }
}

// Wrap your app: <ErrorBoundary><App /></ErrorBoundary>

// 2. Global error handlers
window.onerror = function(message, source, lineno, colno, error) {
  trackError({
    message,
    source,
    line: `${lineno}:${colno}`,
    stack: error?.stack,
  });
};

window.addEventListener('unhandledrejection', (event) => {
  trackError({
    type: 'unhandledRejection',
    reason: String(event.reason),
    stack: event.reason?.stack,
  });
});

// 3. Structured logging
const logger = {
  info: (msg, data) => console.log(JSON.stringify({ level: 'info', msg, ...data })),
  error: (msg, err) => console.error(JSON.stringify({ level: 'error', msg, error: err.message, stack: err.stack })),
  // In production, send to your logging service instead
};

// 4. Correlation IDs (trace requests across services)
const correlationId = crypto.randomUUID();

// Attach to ALL outgoing requests:
fetch(url, { headers: { 'X-Correlation-ID': correlationId } });
// When debugging: search logs by this ID to see the full request flow
Enter fullscreen mode Exit fullscreen mode

What's your favorite debugging technique? What's the weirdest bug you've ever found?

Follow @armorbreak for more practical developer guides.

Top comments (0)