Getting the Most Out of Chrome DevTools
Debugging with console.log is the most basic approach, but it has limitations when tracking down complex bugs. Chrome DevTools provides powerful debugging tools including breakpoints, network analysis, memory profiling, and performance measurement. This article covers the essential DevTools features with practical examples.
Using the console API Properly
There are many more methods beyond console.log. Using the right method for each situation greatly improves debugging efficiency.
// console.table — display arrays/objects in table format
const users = [
{ name: "Alice", role: "admin", active: true },
{ name: "Bob", role: "editor", active: false },
{ name: "Charlie", role: "viewer", active: true },
];
console.table(users);
// ┌─────────┬───────────┬──────────┬────────┐
// │ (index) │ name │ role │ active │
// ├─────────┼───────────┼──────────┼────────┤
// │ 0 │ "Alice" │ "admin" │ true │
// │ 1 │ "Bob" │ "editor" │ false │
// │ 2 │ "Charlie" │ "viewer" │ true │
// └─────────┴───────────┴──────────┴────────┘
// console.group — group related logs together
console.group("Loading user data");
console.log("API call started");
console.log(`URL: /api/users`);
console.warn("Cache miss — fetching from server");
console.groupEnd();
// console.time — measure execution time
console.time("Data processing");
const processed = users.filter((u) => u.active);
console.timeEnd("Data processing");
// Data processing: 0.012ms
// console.assert — only outputs when condition is false
console.assert(users.length > 0, "User list is empty");
console.assert(users.length > 10, "Less than 10 users"); // This prints
// console.trace — trace the call stack
function innerFunction() {
console.trace("Called from here");
}
function outerFunction() {
innerFunction();
}
outerFunction();
// Trace: Called from here
// at innerFunction (app.js:35)
// at outerFunction (app.js:38)
| Method | Use case | Tip |
|---|---|---|
console.table() | Display arrays/objects as tables | Useful for checking API response data |
console.group() | Group logs | Track nested processing flows |
console.time() | Measure execution time | Identify performance bottlenecks |
console.assert() | Conditional output | Verify invariant conditions |
console.trace() | Print call stack | Trace function call paths |
console.count() | Count invocations | Check event frequency |
Using Breakpoints
Breakpoints are a powerful tool that pauses code execution at specific points and lets you inspect variable states.
// 1. debugger keyword — set breakpoints directly in code
function processOrder(order) {
const total = order.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
// Only pause when total is negative
if (total < 0) {
debugger; // Only works when DevTools is open
}
const tax = total * 0.1;
const grandTotal = total + tax;
return { total, tax, grandTotal };
}
// 2. Conditional breakpoints (set in DevTools)
// Sources panel -> right-click line number -> "Add conditional breakpoint"
// Example condition: order.total > 100000
// 3. DOM change breakpoints
// Elements panel -> right-click element -> "Break on"
// - subtree modifications: when child elements change
// - attribute modifications: when attributes change
// - node removal: when element is removed
// 4. XHR/Fetch breakpoints
// Sources panel -> XHR/fetch Breakpoints -> add URL pattern
// Example: pause on requests containing "api/users"
// 5. Event listener breakpoints
// Sources panel -> Event Listener Breakpoints
// Example: check Mouse -> click -> pause on click events
Network Panel
Inspect detailed information about network requests, and track slow or failed requests.
// Fetch interceptor — log all network requests
const originalFetch = window.fetch;
window.fetch = async function (...args) {
const url = typeof args[0] === "string" ? args[0] : args[0].url;
const method =
args[1]?.method || "GET";
console.group(`[Fetch] ${method} ${url}`);
console.time("Response time");
try {
const response = await originalFetch.apply(this, args);
console.log("Status:", response.status);
console.log("Headers:", Object.fromEntries(response.headers));
console.timeEnd("Response time");
if (!response.ok) {
console.error(`HTTP error: ${response.status} ${response.statusText}`);
}
console.groupEnd();
return response;
} catch (error) {
console.error("Network error:", error.message);
console.timeEnd("Response time");
console.groupEnd();
throw error;
}
};
// Key Network panel features:
// - Filters: filter by type (XHR, Fetch, JS, CSS, Img, etc.)
// - Throttling: simulate slow networks (Slow 3G, Offline)
// - Waterfall: visualize request timing
// - Preserve log: keep logs across page navigations
Performance Profiling
Use the Performance panel to find rendering bottlenecks and identify causes of frame drops.
// Set custom markers with the Performance API
function measureRender(componentName) {
performance.mark(`${componentName}-start`);
return {
end() {
performance.mark(`${componentName}-end`);
performance.measure(
componentName,
`${componentName}-start`,
`${componentName}-end`
);
const entries = performance.getEntriesByName(componentName);
const duration = entries[entries.length - 1].duration;
console.log(`[Perf] ${componentName}: ${duration.toFixed(2)}ms`);
// Warn if exceeding 16ms (60fps threshold)
if (duration > 16) {
console.warn(
`[Perf Warning] ${componentName} took ` +
`${duration.toFixed(0)}ms (exceeds 16ms)`
);
}
return duration;
},
};
}
// Usage example
const measure = measureRender("UserListRender");
// ... rendering logic ...
const duration = measure.end();
// [Perf] UserListRender: 23.45ms
// [Perf Warning] UserListRender took 23ms (exceeds 16ms)
// Measuring Web Vitals
function observeWebVitals() {
// Largest Contentful Paint (LCP)
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log(`LCP: ${lastEntry.startTime.toFixed(0)}ms`);
}).observe({ type: "largest-contentful-paint", buffered: true });
// Cumulative Layout Shift (CLS)
let clsScore = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsScore += entry.value;
}
}
console.log(`CLS: ${clsScore.toFixed(4)}`);
}).observe({ type: "layout-shift", buffered: true });
}
Memory Debugging
Memory leaks are common in long-running SPAs. Use DevTools’ Memory panel to detect them.
// Common causes of memory leaks and their solutions
// 1. Unreleased event listeners
class BadComponent {
constructor() {
// Leak: listener remains after component is removed
window.addEventListener("resize", this.handleResize);
}
handleResize = () => {
console.log("resize");
};
}
class GoodComponent {
#controller = new AbortController();
constructor() {
// AbortController enables bulk listener removal
window.addEventListener("resize", this.handleResize, {
signal: this.#controller.signal,
});
}
handleResize = () => {
console.log("resize");
};
destroy() {
this.#controller.abort(); // Remove all listeners at once
}
}
// 2. Leaks caused by closures
function createLeakyHandler() {
const hugeData = new Array(1000000).fill("leak");
// hugeData is kept alive by the closure
return () => console.log(hugeData.length);
}
// 3. Leaks caused by DOM references
const detachedNodes = new Map();
function removeElement(id) {
const element = document.getElementById(id);
detachedNodes.set(id, element); // Reference kept even after DOM removal
element.remove();
// Fix: detachedNodes.delete(id);
}
// Using the Memory panel:
// 1. Take a Heap snapshot -> capture current memory usage
// 2. Perform operations, then take a second snapshot
// 3. Use the Comparison view to see which objects increased
// 4. Check Retainers -> identify what references prevent GC
Useful Shortcuts
// Open DevTools: F12 or Ctrl+Shift+I (Windows)
// Console panel shortcuts
// $_ : result of the last evaluated expression
// $0 : currently selected element in the Elements panel
// $("selector") : shorthand for document.querySelector
// $$("selector") : shorthand for document.querySelectorAll
// copy(obj) : copy an object to the clipboard
// monitor(fn) : log every time a function is called
// monitorEvents(element, "click") : monitor events
// Practical usage examples
// $0.style.border = "2px solid red" // Add red border to selected element
// copy(JSON.stringify(data, null, 2)) // Copy JSON data to clipboard
// monitor(fetchUser) // Trace fetchUser calls
Practical Tips
- Use breakpoints instead of console.log: For complex bugs, stepping through code line by line and inspecting variable states is far more efficient
- Use Logpoints: Right-click a breakpoint and select “Add logpoint” to output logs without modifying code
- Blackboxing: Blackbox library files in the Sources panel so you only debug your own code
- Network conditional filters: Use
status-code:500orlarger-than:1Mto filter specific request conditions - Snippets: Save frequently used debugging code in Sources panel Snippets
- Performance Monitor: Search for “Performance Monitor” in
Ctrl+Shift+Pto monitor real-time CPU, heap, and DOM node count - Coverage: Search for “Coverage” in
Ctrl+Shift+Pto identify unused CSS/JS code