DEV Community

Alex Chen
Alex Chen

Posted on

How to Debug JavaScript Like a Pro

How to Debug JavaScript Like a Pro

Stop using console.log for everything. Here's a better way.

The Mindset

Debugging is not guessing.
Debugging is the scientific method:
1. Observe (what's happening?)
2. Hypothesize (what could cause it?)
3. Test (prove or disprove)
4. Fix (when you know the cause)
Enter fullscreen mode Exit fullscreen mode

1. Chrome DevTools You're Not Using

// Breakpoints (better than console.log!)
// Sources tab → click line number → red dot
// Code pauses here → inspect all variables in Scope panel

// Conditional breakpoints
// Right-click breakpoint → "Edit breakpoint"
// Enter: data.length === 0
// Only pauses when condition is true!

// Logpoint (no pause, just logs!)
// Right-click line → "Add logpoint"
// Enter: 'data:', data
// Logs expression value without stopping execution

// DOM breakpoints
// Elements tab → right-click element → Break on:
//   - subtree modifications
//   - attribute modifications
//   - node removal

// XHR/Fetch breakpoints
// Sources panel → XHR/fetch breakpoints
// Add: /api/users  → breaks on any request containing this string

// Event listener breakpoints
// Sources panel → Event Listener Breakpoints
// Check "click" → breaks on any click event anywhere
Enter fullscreen mode Exit fullscreen mode

2. The Console Is Your REPL

// You can run ANY JavaScript in the console
// While paused at a breakpoint, you have access to ALL variables in scope!

// At breakpoint:
data.length           // Check array length
JSON.stringify(data)  // Pretty-print object
data.map(d => d.id)   // Transform and inspect

// Use $_ to reference last result:
[1, 2, 3].map(x => x * 2)  // [2, 4,6]
$_                           // [2, 4, 6]
$_.length                    // 3

// Copy to clipboard:
copy(data)                  // Now paste anywhere!
copy(JSON.stringify(data, null, 2))

// Query DOM:
$$('button')               // Array of all buttons (like querySelectorAll)
$('form')                   // First form (like querySelector)
$x('//div[@class="card"]')  // XPath selector!
inspect($('button'))        // Highlights element in DevTools
Enter fullscreen mode Exit fullscreen mode

3. Debugging Async Code

// Async stack traces (enable in DevTools)
// Settings → Preferences → Enable "Async stack traces"
// Now you can see WHAT led to the async callback:

async function fetchData() {
  const res = await fetch('/api/data'); // ← error here
  return res.json();
}

// Before: Just shows the .then() handler
// After: Shows the full chain from the original caller!

// Debugging promises:
fetch('/api/data')
  .then(res => {
    debugger; // Pauses here — inspect res
    return res.json();
  })
  .then(data => {
    debugger; // Inspect parsed data
  })
  .catch(err => {
    debugger; // Inspect errors
    throw err;
  });

// Debugging async/await:
async function debugMe() {
  try {
    const data = await fetchData();
    debugger; // Step through with F10/F11
  } catch (err) {
    console.error('Failed:', err);
    debugger; // Inspect error state
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Performance Debugging

// Performance tab → Record → do something → Stop
// See exactly what happened frame by frame

// Or programmatically:
performance.mark('start-fetch');
await fetchData();
performance.mark('end-fetch');
performance.measure('fetch-time', 'start-fetch', 'end-fetch');

const measures = performance.getEntriesByName('fetch-time');
console.log(`Fetch took: ${measures[0].duration}ms`);

// Memory profiling:
// Performance → Check "Memory" checkbox → Record
// Look for: Growing blue heap = memory leak!

// Render performance:
// Rendering tab → Paint profiling
// Find layout thrashing (forced reflows):
const el = document.getElementById('test');
console.log(el.offsetTop);  // Triggers reflow!
el.style.top = '10px';     // Then this forces another reflow
// Fix: batch reads, then batch writes
Enter fullscreen mode Exit fullscreen mode

5. Network Debugging

// Network tab → Filter by type (XHR, JS, CSS, Img)
// Click request → Headers / Preview / Response / Timing

// Reproduce requests:
// Right-click request → Copy as cURL/fetch
// Paste in console to replay!

// Throttle network:
// Network tab → No throttling → Select "Slow 3G"
// Test how your app feels on slow connections

// Offline mode:
// Application tab → Service Workers → Offline checkbox
// Test offline behavior without disconnecting

// Override responses:
// Sources → Overrides → Select folder
// Edit any file locally → changes persist across reloads!
Enter fullscreen mode Exit fullscreen mode

6. Source Maps

// When minified code is impossible to read:
// Source maps map compiled code back to source code

// In production build:
// bundle.js.map should be generated
// DevTools automatically loads it

// Verify source maps work:
// Sources tab → Should see your original files,
// not minified bundle.js

// If source maps are broken:
// webpack.config.js:
module.exports = {
  devtool: 'source-map', // Generates separate .map file
  // or 'cheap-module-source-map' (faster builds)
};
Enter fullscreen mode Exit fullscreen mode

7. Common Bugs & How to Find Them

// Bug 1: "undefined is not a function"
// Cause: Calling method on undefined/null
// Fix: Add optional chaining or check existence
user?.getFullName?.();

// Bug 2: "Cannot read property X of undefined"
// Cause: Nested property access without checking
// Fix: Optional chaining + nullish coalescing
const city = user?.address?.city ?? 'Unknown';

// Bug 3: State not updating (React)
// Cause: Mutating state directly or stale closure
// Fix: Use functional updates
setCount(prev => prev + 1);

// Bug 4: Race conditions
// Cause: Multiple async operations completing out of order
// Fix: AbortController or request IDs
const controller = new AbortController();
fetch(url, { signal: controller.signal });
controller.abort(); // Cancel if no longer needed

// Bug 5: "this" is undefined
// Cause: Arrow functions don't have their own `this`
// Fix: Use regular function when `this` matters
class MyClass {
  onClick = () => {         // Arrow: `this` = instance ✅
    console.log(this.name);
  };

  onClickBad() {            // Regular: depends on call site ⚠️
    console.log(this.name);
  }
}
Enter fullscreen mode Exit fullscreen mode

8. Keyboard Shortcuts That Save Time

F8              Pause/resume script execution
F10             Step over (don't enter functions)
F11             Step into (enter functions)
Shift+F11       Step out (exit current function)
Ctrl+Shift+F    Search across all files
Ctrl+Shift+H    Search & replace across files
Ctrl+`          Toggle console focus
Ctrl+L          Clear console
Ctrl+B          Toggle sidebar
$               Access last result in console
Enter fullscreen mode Exit fullscreen mode

What's your go-to debugging technique?

Follow @armorbreak for more developer content.

Top comments (0)