JavaScriptDebugging in DevTools

Debugging JavaScript

Most JavaScript bugs are diagnosed faster with the debugger than with extra console.log calls. Modern devtools — Chrome, Firefox, Safari, Edge, VS Code — let you pause execution, step line by line, inspect every variable in scope, evaluate expressions live and walk up the call stack. The investment to learn the workflow is small; the time saved is enormous.

The debugger statement

Sprinkling debugger; into your source pauses execution at that line when devtools are open. With devtools closed it is a no-op. Useful for finding the right spot fast, especially in third-party or build-output code where setting a breakpoint by file is awkward.

JS
function calculateTotal(items) {
  let total = 0;
  for (const item of items) {
    debugger;          // pause here while devtools are open
    total += item.price * item.qty;
  }
  return total;
}
Remove before shipping
A stray `debugger` statement does no harm in production, but it will halt anyone running with devtools open. Linters such as ESLint's `no-debugger` rule flag them automatically.
Breakpoints in devtools

Most debugging starts in the Sources panel (Chrome/Edge) or Debugger panel (Firefox). Open the file, click the line number, and execution pauses next time that line runs. Devtools support several breakpoint flavours:

  • Line breakpoint — click the gutter. Most common.

  • Conditional breakpoint — right-click the gutter, "Add conditional breakpoint", enter an expression. The pause only happens when the expression is truthy.

  • Logpoint — like a conditional breakpoint, but instead of pausing it logs a message. The fastest replacement for console.log because you do not have to edit source.

  • DOM breakpoint — pause when an element's subtree changes, attributes change, or the node is removed. In the Elements panel: right-click a node → "Break on".

  • Event listener breakpoint — pause on any click, keydown, setTimeout etc. Set in the right-hand "Event Listener Breakpoints" panel.

  • XHR / fetch breakpoint — pause when a request URL matches a substring you provide.

  • Exception breakpoint — pause on every thrown error, optionally only uncaught ones.

Stepping controls

Once you are paused, five buttons (or keyboard shortcuts) drive everything:

  • Resume (F8) — continue running until the next breakpoint.

  • Step over (F10) — run the current line and stop on the next one in the same function.

  • Step into (F11) — enter the function call on this line.

  • Step out (Shift+F11) — finish the current function and stop in its caller.

  • Step (F9) — like step into, but also descends into async continuations and timers.

A common rhythm: set a breakpoint a few lines before the suspected bug, hit reload, step over until something looks wrong, step into the offending call.

Reading the call stack and scope

While paused, the Call Stack panel shows every frame above the current line. Click a frame to switch into it — the Scope panel updates to show that frame's local, closure and global bindings. This is how you answer "which arguments did the caller pass me?" without adding logging.

  • Local — variables declared in the current function.

  • Closure — variables captured from enclosing functions.

  • Script / Module — top-level bindings in the file.

  • Global — the global object (window, globalThis, process).

Right-click any frame in the call stack for Copy stack trace, Restart frame (re-run that function from the top) and Blackbox script (skip stepping into a noisy library).

The Watch panel and live expressions

Pin expressions in the Watch panel to see them recomputed at every pause: user.preferences.theme, items.filter(i => i.dirty).length, anything. The Console also has access to the paused scope — try expressions there before adding them to the watch list.

JS
// While paused inside calculateTotal, evaluate in the console:
items.length
items.filter((i) => i.qty > 0).length
items.reduce((s, i) => s + i.price * i.qty, 0)
copy(items)        // copies the value to your clipboard as JSON
Conditional and log breakpoints in practice

Two patterns that pay off on day one:

  • Conditional breakpoint when a loop misbehaves on a specific iteration: condition i === 137 or item.id === "abc". Skips the first 137 hits automatically.

  • Logpoint instead of console.log("x:", x): the expression is "x:", x and devtools logs it without editing source — no save, no rebuild, no risk of leaving the log in.

Async stack traces and source maps

Modern devtools stitch together the async call stack — when a callback runs, you can see what scheduled it, all the way back through promises and timers. Enable "Async stack traces" if it is not on by default. For minified or transpiled code, devtools follow source maps transparently so you can step through your original source even though the browser is running compiled output.

Debugging Node.js

Node speaks the same Chrome Devtools protocol. Run with node --inspect-brk script.js and open chrome://inspect to attach. VS Code's "JavaScript Debugger" extension does this automatically when you launch with the Debug tab. Most browser techniques (breakpoints, watch, conditional breaks) work identically server-side.

Performance and memory views
  • Performance panel — record a CPU profile. Find slow functions in the flame chart.

  • Memory panel — take heap snapshots, find retained objects, detect leaks.

  • Network panel — request waterfall, headers, timing. Useful for debugging async sequencing.

  • Application panel — cookies, storage, service workers, cache.

Habit to build
The next time you reach for `console.log`, set a breakpoint instead. Two or three pauses with a working scope and call-stack panel teaches you more about your program than ten log lines. Once it becomes muscle memory, you will not go back.