← All Articles · · 14 min read

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.

javascriptdebuggingchrome-devtoolsweb-developmentfrontenddeveloper-tools

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:

  1. Keyboard: F12 or Ctrl+Shift+I (Windows/Linux), Cmd+Option+I (Mac)
  2. Right-click: Right-click any element → “Inspect”
  3. 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

  1. Open Sources panel
  2. Find your file in the file tree (left sidebar) or press Ctrl+P to search by filename
  3. 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:

ButtonKeyboardWhat it does
ResumeF8Continue execution until next breakpoint
Step OverF10Execute current line, pause on next
Step IntoF11Enter the function call on this line
Step OutShift+F11Complete current function, pause in caller
StepF9Execute 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
  • Globalwindow properties

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/users finds any request with that string in the URL
  • Negative filter: -static excludes 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 for document.querySelector(selector)
  • $$(selector) — shortcut for document.querySelectorAll(selector) (returns array)

Performance Debugging

Performance Panel Basics

When code is slow, the Performance panel shows exactly where time is spent.

  1. Open Performance panel
  2. Click record (⏺)
  3. Do the slow action
  4. 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:

  1. Take a Heap snapshot at time A
  2. Do the action suspected of leaking
  3. Take another snapshot
  4. 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:

  1. Find the minified file in Sources
  2. Right-click → “Add source map…”
  3. 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.

  1. Sources → Filesystem → “Add folder to workspace”
  2. Select your project directory
  3. 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

ActionWindows/LinuxMac
Open DevToolsF12 or Ctrl+Shift+ICmd+Option+I
Open ConsoleCtrl+Shift+JCmd+Option+J
Toggle Device ModeCtrl+Shift+MCmd+Shift+M
Search fileCtrl+P (in Sources)Cmd+P
Search all filesCtrl+Shift+FCmd+Option+F
Resume executionF8F8
Step overF10F10
Step intoF11F11
Step outShift+F11Shift+F11
Evaluate selectionSelect code + Ctrl+Shift+ECmd+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.