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.
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;
}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.logbecause 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,setTimeoutetc. 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.
// 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 === 137oritem.id === "abc". Skips the first 137 hits automatically.Logpoint instead of
console.log("x:", x): the expression is"x:", xand 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.