Breakpoints and Stepping
Breakpoints let you pause JavaScript while it is running.
When execution pauses, you can inspect variables, scope, closures, this, the call stack, and the exact line that will run next.
This is often better than adding many console.log() calls.
Why Use Breakpoints?
Logs show selected values after you decide what to print.
Breakpoints let you explore the current state while the program is paused.
Use breakpoints when:
- you do not know which value is wrong yet
- the code path is complicated
- a function is called from several places
thisor closure state is confusing- a loop changes data over time
- an async callback runs later than expected
Setting a Breakpoint
Open the Sources panel in DevTools.
Find the JavaScript file you want to inspect.
Click the line number where you want execution to pause.
When the browser reaches that line, it pauses before running it.
function calculateTotal(items) {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
const tax = subtotal * 0.08;
return subtotal + tax;
}If the result is wrong, set a breakpoint on the subtotal line or the return line and inspect items, subtotal, and tax.
Step Controls
When code is paused, DevTools usually shows several controls.
| Control | Meaning |
|---|---|
| Resume | Continue running until the next breakpoint |
| Step over | Run the current line without entering called functions |
| Step into | Enter the function called on the current line |
| Step out | Finish the current function and pause where it returns |
| Restart frame | Run the current function frame again, when supported |
Use Step over when the function call is not relevant.
Use Step into when the bug may be inside that function.
Use Step out when you stepped too deep and want to return to the caller.
Inspecting Variables
While paused, hover over variables or use the Scope panel.
function applyCoupon(cart, coupon) {
const discount = cart.total * coupon.percent;
return cart.total - discount;
}If discount is NaN, inspect:
cartcart.totalcouponcoupon.percent
The wrong output is usually caused by a wrong input, a wrong assumption, or a missed state change.
Watching Expressions
The Watch panel lets you track expressions while stepping.
Examples:
cart.items.length
user?.profile?.email
typeof total
thisWatch expressions are useful when the value is nested or when it changes across several steps.
The Call Stack
The call stack shows how execution reached the paused line.
function validateOrder(order) {
return hasValidAddress(order.shippingAddress);
}
function hasValidAddress(address) {
return address.city.length > 0;
}If the debugger pauses inside hasValidAddress, the call stack shows that validateOrder called it.
This helps when a function is correct for some callers but not for others.
Inspecting Scope and Closures
JavaScript functions remember variables from outer scopes.
function createCounter() {
let count = 0;
return function increment() {
count += 1;
return count;
};
}When paused inside increment, DevTools can show the closure scope containing count.
This is useful when a closure keeps old state or when multiple functions share the same closed-over variable.
Inspecting this
this depends on how a function is called.
const user = {
name: "Ari",
greet() {
return `Hi, ${this.name}`;
},
};
const greet = user.greet;
greet();If you pause inside greet, inspect this.
In this detached call, this is not the user object, so this.name will not behave as expected.
Breakpoints make these context problems easier to see than reading the code alone.
Conditional Breakpoints
A conditional breakpoint pauses only when an expression is true.
This is useful in loops.
for (const user of users) {
processUser(user);
}Instead of pausing for every user, create a conditional breakpoint:
user.id === 42Now the debugger pauses only for the user you care about.
Logpoints
Some DevTools support logpoints.
A logpoint prints a message without pausing execution and without changing your source file.
This is useful when you want temporary logging but do not want to edit code.
Example message:
"processing user", user.id, user.roleDebugger Statement
You can also pause code by adding a debugger statement.
function submitForm(data) {
debugger;
return sendForm(data);
}When DevTools is open, execution pauses on that line.
Remove debugger statements before committing or shipping code.
Breakpoints in Event Handlers
If a bug happens after a click, submit, key press, or timer, set a breakpoint inside the handler.
button.addEventListener("click", (event) => {
event.preventDefault();
savePreferences();
});Pause inside the handler and inspect:
- the
eventobject - selected DOM elements
- form values
- state variables
- the call stack
Common Mistakes
Do not set a breakpoint after the bug already happened. Pause before the suspicious value is created or changed.
Do not step into every library function. Step over code that is not relevant to your investigation.
Do not ignore the call stack. It often explains why a function received an unexpected value.
Do not forget that paused code stops the page. Timers, animations, and pending interactions may wait while the debugger is paused.
Summary
Breakpoints let you inspect JavaScript at the exact moment it runs.
Use them to:
- pause before suspicious code
- inspect variables, scope, closures, and
this - follow the call stack
- step into or over functions
- use conditional breakpoints for noisy loops
- debug event handlers and async callbacks
The debugger is strongest when you use it to follow evidence one line at a time.