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)
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
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
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
}
}
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
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!
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)
};
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);
}
}
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
What's your go-to debugging technique?
Follow @armorbreak for more developer content.
Top comments (0)