How to Debug JavaScript Like a Pro
Console.log is fine. But these techniques are better.
Level 1: Console Methods Beyond log
// console.table — Beautiful data display
const users = [
{ name: 'Alex', age: 30, role: 'dev' },
{ name: 'Sam', age: 25, role: 'design' },
{ name: 'Charlie', age: 35, role: 'dev' },
];
console.table(users);
// console.group — Group related logs
console.group('API Request');
console.log('URL:', url);
console.log('Method:', method);
console.log('Headers:', headers);
console.groupEnd();
// console.time — Measure performance
console.time('fetchData');
await fetchData();
console.timeEnd('fetchData'); // fetchData: 1.234s
// console.assert — Silent unless false
console.assert(user.age >= 18, 'User is underage!', user);
// console.count — Count how many times called
function handleData(data) {
console.count('handleData called');
}
// handleData called: 1
// handleData called: 2
// console.trace — Show call stack
function deepFunction() {
console.trace('Here!');
}
// console.dir — Object properties (non-JSON-safe)
console.dir(document.body);
// Styled output
console.log(
'%c ERROR! %c Something went wrong',
'color: red; font-weight: bold; font-size: 14px',
'color: gray'
);
// String substitution
const name = 'Alex';
const count = 42;
console.log('User %s has %d items', name, count); // User Alex has 42 items
Level 2: Chrome DevTools Breakpoints
// Don't use debugger; statement in production!
// But in DevTools Sources panel:
// Regular breakpoint — click line number
// Conditional breakpoint — right-click → "Add conditional breakpoint"
// → Enter condition: data.length > 1000
// → Only pauses when condition is true
// Logpoint — right-click → "Add logpoint"
// → Enter: 'data.length:', data.length
// → Logs without pausing execution!
// DOM breakpoint — Elements panel → right-click element
// → Break on: subtree modifications, attribute changes, node removal
// XHR/Fetch breakpoint — Sources panel → XHR breakpoints
// → Add URL pattern containing: /api/users
// → Pauses on any matching request
Level 3: Network Tab Mastery
Network tab columns:
┌──────────┬──────────┬───────┬──────────┬────────┐
│ Name │ Status │ Type │ Time │ Size │
├──────────┼──────────┼───────┼──────────┼────────┤
│ api/user │ 200 │ xhr │ 234ms │ 1.2 KB │
│ app.js │ 304 │ js │ 45ms │ 45 KB │
│ style.css│ 200 │ css │ 12ms │ 2.1 KB │
└──────────┴──────────┴───────┴──────────┴────────┘
Key features:
- Right-click request → Copy as fetch/curl
- Preview tab: JSON formatted
- Response tab: Raw response
- Timing tab: Waterfall breakdown (DNS, TCP, TTFB, Download)
- Disable cache checkbox (top of network tab)
- Filter by type: Fetch/XHR/JS/CSS/Img/Font
- Preserve log (don't clear on navigation)
- Slow 3G / Fast 3G throttling
Level 4: Source Maps & Pretty Print
// Minified code? Click {} button in Sources tab
// → Pretty prints minified code for readability
// Source maps enabled?
// Check Sources panel → Page tree → look for original .ts/.vue files
// If not showing:
// → Check webpack/vite config for devtool: 'source-map'
// → Verify .map file exists alongside .js file
// Blackbox scripts you don't care about:
// Settings → Blackboxing → Add pattern: node_modules/*
// → Step through your code, skip library code
Level 5: Performance Profiling
// Performance tab: Record → Interact → Stop → Analyze
// Find slow functions:
// Call tree view → Sort by "Self time" (not total)
// → Functions with high self-time = optimization targets
// Memory leaks:
// Memory tab → Take heap snapshot
// Do action → Take another snapshot
// Compare snapshots → Look for detached DOM nodes + growing arrays
// Render performance:
// Rendering tab → Enable "Paint flashing"
// → Green flash = repaint (expensive if frequent)
// JS profiling in code:
performance.mark('startProcess');
doExpensiveWork();
performance.mark('endProcess');
performance.measure('process', 'startProcess', 'endProcess');
const measures = performance.getEntriesByName('process');
console.log(measures[0].duration); // ms
Level 6: Common Bugs & How to Find Them
"TypeError: Cannot read property X of undefined"
// The bug
const user = apiResponse.data.user;
user.name; // Crash if data or user is undefined
// Quick debug: Log each level
console.log({
hasResponse: !!apiResponse,
hasData: !!apiResponse?.data,
hasUser: !!apiResponse?.data?.user,
});
// Fix: Optional chaining + nullish coalescing
const name = apiResponse?.data?.user?.name ?? 'Anonymous';
"My async function returns undefined"
// The bug
async function getData() {
const res = await fetch('/api/data');
res.json(); // Missing return!
}
// Fix
async function getData() {
const res = await fetch('/api/data');
return await res.json(); // ← Return the promise result
}
// Or even simpler
async function getData() {
const res = await fetch('/api/data');
return res.json(); // No need for double-await
}
"State not updating in React"
// The bug
function handleClick() {
setCount(count + 1);
console.log(count); // Shows OLD value (batched!)
}
// Fix: Use functional update
function handleClick() {
setCount(prev => prev + 1);
}
// Or check effect
useEffect(() => {
console.log(count); // Shows NEW value
}, [count]);
"This is undefined in callback"
// The bug
class MyClass {
value = 42;
print() {
setTimeout(function() {
console.log(this.value); // undefined!
}, 100);
}
}
// Fix 1: Arrow function (inherits this)
setTimeout(() => {
console.log(this.value); // Works!
}, 100);
// Fix 2: Bind
setTimeout(this.print.bind(this), 100);
// Fix 3: Class field arrow
print = () => {
console.log(this.value);
};
What's your favorite debugging technique? Share below!
Follow @armorbreak for more JavaScript content.
Top comments (0)