Breakpoints and stepping
Pause the code at the line you care about, look at every variable, then walk forward one step at a time.
You added a console.log to figure out what a variable holds. The log fires once. The value is wrong. So you add three more logs. Two more reloads. Now you have output but no clarity — you can see the values, but you can't ask follow-up questions like "what was the previous value?" or "what called this function?".
A breakpoint solves this in a single move. You tell devtools "pause the page when this line is about to run." The browser stops there, lets you inspect every variable in scope, lets you type in the Console as if you were standing inside the function, and lets you walk forward one line at a time.
This lesson covers six kinds of breakpoint. The first one (a line breakpoint) is what most people use. The other five are the difference between fishing with a single hook and fishing with the right tool for the catch.
Line breakpoints
A line breakpoint pauses execution right before a specific line runs. To set one:
- Open Sources in devtools.
- Find the file in the file tree on the left.
- Click the line number in the gutter — a blue marker appears.
Now reload the page or trigger the code. The browser pauses. The line is highlighted. The right side fills with three useful pieces:
- Scope — every variable currently in scope, with its current value. Local variables, the closure they were defined in, globals.
- Call Stack — the chain of functions that led here. The top entry is the current function; below it is whoever called it; and so on. Click any frame to jump back into that function's scope.
- Watch — a list of expressions you've pinned. Each one is re-evaluated every time you step.
function applyDiscount(cart, code) {
const rule = findRule(code);
const eligible = cart.filter(item => rule.appliesTo(item));
/* set a breakpoint here ↓ */
const total = eligible.reduce((sum, item) => sum + item.price, 0);
return total * rule.percent;
}When the browser pauses on the breakpoint, you can hover any variable in the source — cart, eligible, rule — and a little tooltip shows its current value. Or open the Console and type eligible.length to see how many items survived the filter. You're standing inside the function. Anything in scope is yours to inspect.
Hovering over a variable in the source view is faster than scrolling the Scope panel. For nested objects, the tooltip is fully expandable — click into it and you can drill down through the whole object graph.
Stepping through
Once paused, four buttons control how you move forward. They sit at the top of the right column with keyboard shortcuts.
- Resume (F8) — continue running until the next breakpoint (or the page goes idle).
- Step over (F10) — run the current line, then pause again at the next line in this function. If the line calls another function, it runs that function fully and stops at the line after the call.
- Step into (F11) — if the current line calls a function, jump inside that function and pause at its first line. Use this when you suspect the bug is in the function you're about to call.
- Step out (Shift+F11) — finish the current function and pause at the line that called it. Useful when you stepped into something by accident and want to back out.
A typical session: set a breakpoint where the bug seems to live, reload, look around, hit Step Over a few times to see how variables change line by line, hit Step Into when a suspicious-looking function gets called, hit Step Out when you've seen enough.
A practical analogy: stepping is like reading a paper map of your code, where Resume is "drive to the next stop", Step Over is "drive past this town without stopping", Step Into is "exit and explore this town", and Step Out is "leave this town and rejoin the highway."
Conditional breakpoints
A line breakpoint pauses every time the line runs. Inside a loop with a thousand items, that's a thousand pauses. You don't want that.
A conditional breakpoint pauses only when an expression you write is true. Right-click a line number → Add conditional breakpoint → type an expression.
for (const order of orders) {
/* line breakpoint here pauses 5,000 times.
Conditional breakpoint with: order.id === 'ORD-9482'
pauses exactly once — when the bad order shows up. */
processOrder(order);
}The expression is a boolean. The browser evaluates it before the line runs; if it's truthy, it pauses; otherwise it continues. The expression has access to every variable in scope at that line, just like the Console does when paused.
This is the breakpoint you reach for when a bug only happens for a specific user, a specific item, or a specific input.
A conditional that throws an error is silently treated as false — the breakpoint just never pauses. If you set one and it doesn't fire, double-check the expression for typos: order.iD instead of order.id won't error, it'll just always be undefined and never equal to anything.
status field is "refunded". What's the right breakpoint?Logpoints — log without editing the file
A logpoint is a breakpoint that logs instead of pausing. Right-click the line → Add logpoint → type any expression (no console.log needed; just the value or message you want).
It does the same thing as console.log("user:", user) would, except the line number doesn't change in your file, and you can add or remove logpoints without saving anything.
Two reasons to prefer logpoints over a console.log:
- You don't risk committing them. Forget a
console.login your code and it goes to production. A logpoint lives only in devtools. - They work in code you don't own. Add a logpoint to a third-party library script and watch its internals without modifying the bundle.
A logpoint expression can be a simple value (user) or an interpolated string with {} for expressions: clicked {event.target.tagName} at {Date.now()}.
DOM breakpoints — pause when the page changes
Sometimes the bug is "this element keeps disappearing and I don't know who's removing it." Searching for .remove() calls is hopeless if you have hundreds of files.
DOM breakpoints fix this from the other end. Right-click any element in the Elements panel → Break on... with three choices:
- Subtree modifications — pause when any descendant of this element is added or removed.
- Attribute modifications — pause when this element's attributes change (a class added, an
srcswapped). - Node removal — pause when this exact element is removed from the DOM.
When the breakpoint fires, the Sources panel opens, the page is paused, and the Call Stack shows you exactly which function did the deed. You walk the stack backward until you find the culprit.
/* Somewhere in your codebase, this runs.
You don't know where, but the DOM breakpoint
on the Elements panel will pause execution
right here and the Call Stack will tell you who. */
document.querySelector('.error-banner').remove();XHR and event-listener breakpoints
Two more kinds, useful for very specific situations.
XHR/fetch breakpoints pause when a network request is about to be sent, optionally filtered by a URL substring. You set them in the Sources panel sidebar under "XHR/fetch Breakpoints" → click the + → type a URL fragment like /api/orders. The next time any code calls fetch('/api/orders/...'), the page pauses just before the request goes out, and the Call Stack tells you who triggered it.
This is the breakpoint you want when an unexpected API call is hitting your backend and you can't find what's making it.
Event-listener breakpoints pause when any handler for a specific kind of event runs. The sidebar has a tree of event types — click, mouse, keyboard, scroll, etc. Check click and the next click anywhere on the page pauses inside the first handler that runs.
This is how you find the click handler for a button when there's no obvious data attribute or function name to grep for.
error-banner keeps disappearing from the page after a couple of seconds, and you can't find what's removing it. What's the most direct way to find the culprit?order.iD === 'ABC' (typo: capital D in iD). The breakpoint never pauses. What happened?computeTotals(orders), but you want to step *into* computeTotals the moment it's called — not over it. Which key?