Chrome DevTools JavaScript Debugging: Complete Guide (2026)
Master JavaScript debugging in Chrome DevTools. Learn breakpoints, the Call Stack, watch expressions, network inspection, and performance profiling with practical examples.
Every developer has the console.log habit. It works—until it doesn’t. When a bug is hiding in a 500ms async chain, behind a third-party callback, or inside a minified library, console.log becomes a dead end.
Chrome DevTools has a full-featured debugger built into the browser. Most developers use 5% of it. This guide covers the other 95%—the features that actually eliminate the need for most console.log debugging.
Opening DevTools
Three ways to open Chrome DevTools:
- Keyboard:
F12orCtrl+Shift+I(Windows/Linux),Cmd+Option+I(Mac) - Right-click: Right-click any element → “Inspect”
- Menu: Chrome menu → More tools → Developer tools
The Sources panel is your primary debugging environment. The Console is where you interact with live code. The Network panel handles API inspection.
Breakpoints: The Foundation
A breakpoint pauses execution at a specific line so you can inspect the runtime state—variables, the call stack, scope chain—everything.
Setting a Line Breakpoint
- Open Sources panel
- Find your file in the file tree (left sidebar) or press
Ctrl+Pto search by filename - Click the line number in the gutter
A blue marker appears. Now reload the page (or trigger the code path), and execution pauses at that line.
While paused, you can:
- Hover over any variable to see its current value
- Inspect the Scope panel (bottom-right) to see all variables in scope
- View the Call Stack to see how execution got here
Conditional Breakpoints
Right-click a line number → “Add conditional breakpoint” → Enter an expression.
// Only break when userId is undefined
userId === undefined
// Only break on the 5th iteration
i === 4
// Only break when the response has an error
response.status >= 400
This is far more useful than if (condition) console.log(...) scattered throughout your code.
Logpoints (Console.log Without Editing Code)
Right-click a line → “Add logpoint” → Enter a message template.
User ID: {userId}, Status: {response.status}
This logs to the Console when execution hits that line—without pausing, without modifying your code. It survives page reloads. Perfect for adding temporary logging to production bundles.
DOM Breakpoints
In the Elements panel, right-click any DOM node → Break on → choose:
- Subtree modifications — fires when any child is added, removed, or changed
- Attribute modifications — fires when any attribute on the element changes
- Node removal — fires when the element itself is removed
This is invaluable for debugging “something is changing this element and I don’t know what.”
The Debugger Statement
Add debugger; anywhere in your JavaScript code:
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
debugger; // Execution pauses here when DevTools is open
const data = await response.json();
return data;
}
When DevTools is open, this behaves exactly like a line breakpoint. When DevTools is closed, it’s silently ignored.
Stepping Through Code
Once paused at a breakpoint, you have several navigation options:
| Button | Keyboard | What it does |
|---|---|---|
| Resume | F8 | Continue execution until next breakpoint |
| Step Over | F10 | Execute current line, pause on next |
| Step Into | F11 | Enter the function call on this line |
| Step Out | Shift+F11 | Complete current function, pause in caller |
| Step | F9 | Execute one statement |
Practical use:
- Use Step Over to move line by line without entering function bodies
- Use Step Into when you need to trace inside a function call
- Use Step Out when you’ve stepped into something you don’t need to trace further
Run to Here
Right-click any line → “Continue to here.” Execution resumes and pauses at that line without needing a breakpoint. Great for skipping over sections you don’t care about.
Inspecting Variables
The Scope Panel
While paused, the Scope panel (bottom of Sources) shows:
- Local — variables declared in the current function
- Closure — variables from outer function scopes
- Global —
windowproperties
You can expand objects to browse their properties.
Watch Expressions
Click the + in the Watch panel and enter any JavaScript expression:
user.roles.includes('admin')
response.headers.get('content-type')
items.filter(i => i.selected).length
Object.keys(state).filter(k => state[k] === null)
Watch expressions re-evaluate on every step, so you can track complex conditions as you walk through code.
The Console While Paused
The Console is live while execution is paused. You have full access to all in-scope variables:
// While paused inside a function with `user` in scope:
> user
{ id: 42, name: 'Alice', role: 'admin' }
> user.role === 'admin'
true
> JSON.stringify(user, null, 2)
'{\n "id": 42,\n ...'
// Mutate values to test different branches
> user.role = 'viewer'
'viewer'
// Then resume — code will execute the viewer branch
This is arguably the most powerful debugging technique. You can test hypotheses and force code down different branches without reloading.
The Call Stack
The Call Stack panel shows the sequence of function calls that led to the current breakpoint.
fetchUser ← current position
handleFormSubmit
onClick (event)
Click any frame in the call stack to jump to that function’s context. The Scope panel updates to show variables as they existed at that point in the chain.
This is essential for answering “how did we get here?”—especially useful when a generic error handler is invoked and you need to trace back to the originating call.
Blackboxing Scripts
Third-party libraries (React, lodash, etc.) can clutter the call stack with frames you don’t care about. Blackboxing hides them.
Right-click any frame from a library → “Add script to ignore list.” Or go to Settings → Ignore List and add URL patterns like node_modules.
After blackboxing, Step Into skips over library internals, and the call stack only shows your code.
Debugging Async Code
Async Stack Traces
Chrome DevTools automatically shows async call chains. When paused in a callback or Promise handler, the call stack shows the Async section with the chain that initiated the async operation.
async function main() {
const user = await getUser(1); // ← initiator shown in async trace
}
async function getUser(id) {
const response = await fetch(`/api/users/${id}`);
debugger; // Paused here — async trace shows main() above
return response.json();
}
Event Listener Breakpoints
In the Sources panel → right sidebar → Event Listener Breakpoints. Expand categories like “Mouse”, “Keyboard”, “XHR/Fetch” to set breakpoints on any event type.
For example, enable XHR/fetch breakpoints → “Request is sent” to pause on every network call and inspect the request before it goes out.
Or enable click under Mouse to find which handler fires on a specific click—even if the click handler is dynamically added and you can’t find it in the source.
Network Panel: Debugging API Calls
Inspecting Requests
The Network panel logs every request the page makes. Click any request to see:
- Headers — request headers, response headers, status code
- Payload — request body (for POST/PUT/PATCH)
- Response — raw response body
- Timing — breakdown of connection, DNS, TTFB, download time
Filtering Requests
The filter bar at the top supports:
- Type filters: XHR, Fetch, JS, CSS, Img, etc.
- Text search:
api/usersfinds any request with that string in the URL - Negative filter:
-staticexcludes URLs containing “static”
Throttling Network Speed
DevTools can simulate slow connections. In the Network panel toolbar, click “No throttling” and choose:
- Slow 3G — 50 KB/s down, 50 KB/s up, 400ms RTT
- Fast 3G — 1.5 MB/s down, 750 KB/s up, 40ms RTT
- Offline — cuts all network access
Use this to reproduce loading states, catch race conditions, and verify that your skeleton screens and loading indicators work correctly.
Replay Requests
Right-click any request → “Replay XHR” to resend it. Or copy it as:
- cURL — paste into terminal to test with curl
- Fetch — paste into Console to test variations in JavaScript
Console: Beyond console.log
Useful Console Methods
// Group related output
console.group('fetchUser(42)');
console.log('Request started');
console.log('Response received', response);
console.groupEnd();
// Table for arrays/objects
console.table(users);
// Outputs a sortable table with columns for each property
// Timing
console.time('data processing');
processData(largeArray);
console.timeEnd('data processing');
// → data processing: 234.56ms
// Count calls
function onClick() {
console.count('button clicked');
}
// → button clicked: 1
// → button clicked: 2
// Assert — logs only if condition is false
console.assert(user.id !== null, 'User ID should not be null', user);
// Object snapshot (vs live reference)
console.log(JSON.parse(JSON.stringify(obj))); // Snapshot at this moment
console.log(obj); // Live reference — shows current state when expanded
Console as a REPL
The Console has access to the entire page’s JavaScript environment:
// Select DOM elements
document.querySelector('.submit-btn').click()
// Access React internals (development build)
$r // Currently selected component in React DevTools
// Run queries against in-memory data
localStorage.getItem('auth_token')
// Modify global state for testing
window.__store__.dispatch({ type: 'RESET' })
$_ and $0 Shortcuts
$_— the last evaluated expression result$0— the currently selected DOM element in the Elements panel$1,$2,$3,$4— previously selected elements$(selector)— shortcut fordocument.querySelector(selector)$$(selector)— shortcut fordocument.querySelectorAll(selector)(returns array)
Performance Debugging
Performance Panel Basics
When code is slow, the Performance panel shows exactly where time is spent.
- Open Performance panel
- Click record (⏺)
- Do the slow action
- Stop recording
The flame chart shows:
- Yellow — JavaScript execution
- Purple — style recalculation and layout
- Green — painting
- Gray — idle time
Click any frame to zoom in. Find the longest bars—those are your bottlenecks.
Performance Monitor (Live)
View → “Performance monitor” shows real-time metrics including:
- CPU usage
- JS heap size
- DOM node count
- Layout/style recalculations per second
Watch for memory that only grows—that’s a leak.
Memory Snapshot
In the Memory panel:
- Take a Heap snapshot at time A
- Do the action suspected of leaking
- Take another snapshot
- Switch to “Comparison” view
The comparison shows objects allocated since snapshot A that haven’t been garbage collected. If the count keeps growing with each action, you have a leak.
Source Maps and Production Debugging
Minified production code is debuggable if source maps are deployed. DevTools automatically fetches source maps referenced in the minified file:
//# sourceMappingURL=app.js.map
With source maps, you see original TypeScript or unminified JavaScript in the Sources panel—not the minified output. Set breakpoints in the original source, inspect variable names as written.
If you can’t deploy source maps to production, you can load them locally:
- Find the minified file in Sources
- Right-click → “Add source map…”
- Point to a local file or private URL
Workspace: Edit Files Directly in DevTools
DevTools can map browser-loaded files to files on disk, enabling edits that persist.
- Sources → Filesystem → “Add folder to workspace”
- Select your project directory
- Grant access when prompted
Now edits in DevTools save to disk, and the browser reflects changes without reloading. Changes made to CSS apply instantly. This is particularly useful for rapid CSS iteration.
Keyboard Shortcuts Reference
| Action | Windows/Linux | Mac |
|---|---|---|
| Open DevTools | F12 or Ctrl+Shift+I | Cmd+Option+I |
| Open Console | Ctrl+Shift+J | Cmd+Option+J |
| Toggle Device Mode | Ctrl+Shift+M | Cmd+Shift+M |
| Search file | Ctrl+P (in Sources) | Cmd+P |
| Search all files | Ctrl+Shift+F | Cmd+Option+F |
| Resume execution | F8 | F8 |
| Step over | F10 | F10 |
| Step into | F11 | F11 |
| Step out | Shift+F11 | Shift+F11 |
| Evaluate selection | Select code + Ctrl+Shift+E | Cmd+Shift+E |
Key Takeaways
- Conditional breakpoints eliminate most
if (x) console.log()patterns - Logpoints add logging without touching your source code
- DOM breakpoints find what’s modifying elements when you can’t track it down
- Watch expressions track complex state as you step
- The Console is live while paused—use it to test hypotheses and mutate values
- Blacklist/ignore third-party scripts to keep the call stack clean
- XHR breakpoints pause on every network call—great for tracing async problems
- Source maps make production code debuggable
The single highest-leverage debugging skill: pause at a breakpoint, open the Console, and start interrogating the runtime state. You can reproduce any condition, test any hypothesis, and fix bugs in minutes instead of hours.
Free Newsletter
Level Up Your Dev Workflow
Get new tools, guides, and productivity tips delivered to your inbox.
Plus: grab the free Developer Productivity Checklist when you subscribe.
Found this guide useful? Check out our free developer tools.
Affiliate disclosure: Some links below are affiliate links — we may earn a small commission at no extra cost to you. Learn more.
Recommended Tools & Resources
DigitalOcean
$200 credit for new users. Simple, affordable cloud hosting for developers.
GitHub Student Pack
Free access to 100+ developer tools. Perfect for students and new devs.
Vercel
Deploy frontend apps instantly. Free tier is generous for side projects.
DevPlaybook Products
Boilerplates, scripts & AI toolkits to 10x your dev workflow.