Debugging in the browser
Pause JavaScript with browser breakpoints, step through function calls, and inspect values with Scope and Watch panels.
- 01Pause code on purposeUse line breakpoints, conditional breakpoints, and debugger statements.
- 02Move through a paused programChoose Step over, Step into, Step out, or Resume for the question you are asking.
- 03Inspect values while pausedRead Local, Closure, and Global scope sections, and pin Watch expressions.
Debugging means pausing the movie
A bug is a moment when the program and your expectation split apart. The browser debugger helps you find that moment by pausing JavaScript while it is running. Once paused, you can read the current line, move one step at a time, and inspect the values the program is actually using.
Earlier lessons used the DevTools console and console.log to print clues. That is still useful. But printing is like asking an actor to shout notes while the play continues. A breakpoint is different: it freezes the scene so you can look around.
The step-by-step player in this course is a guided replay of code we recorded. It is not a debugger. This lesson uses that replay to explain the ideas, then asks you to try the real debugger in your own browser DevTools.
Imagine a mystery movie. If you let it play at full speed, you might miss the clue on the desk. Pause at the right frame and the clue is obvious. Browser debugging is the same habit for code: stop at one statement, look carefully, then continue.
- In real life: Pause at the exact scene
- In JavaScript: Set a line breakpoint on the exact statement
- In real life: Look at props and actors
- In JavaScript: Inspect variables in Scope
- In real life: Advance one frame
- In JavaScript: Step over or step into the next operation
Where the analogy stops: A movie frame is passive. A paused program is live: changing values in DevTools can change what happens next, so beginners should inspect first and edit only when they mean to.
Open Chrome or Edge DevTools and choose Sources. In Firefox, the panel is called Debugger. In Safari, use Sources after enabling developer features. The names vary, but the ideas in this lesson are the same.
Breakpoints: tell the browser where to pause
BREAKPOINTSA breakpoint is a marker you place on code. When JavaScript reaches that spot, DevTools pauses before moving on. In most browsers you set a line breakpoint by opening a file in the debugging panel and clicking the line number.
Useful breakpoint types include:
- Line breakpoint: pause every time a line runs.
- Conditional breakpoint: pause only when an expression is true, such as
i === 50in a loop. - Logpoint: print a message from that line without pausing and without editing the file.
- Pause on exceptions: stop where an error is thrown. Many browsers can also pause on caught exceptions.
- DOM and event listener breakpoints: pause when a page node changes or an event such as a click fires. These are browser features around JavaScript, not new JavaScript syntax.
// Line breakpoint: pause whenever this line runs.total = total + price; // Conditional breakpoint: pause only when this is true.i === 50 // Logpoint: print a message without pausing."total is", total // Pause on exceptions: stop where an error is thrown.throw new TypeError("price must be a number");| Tool | When it helps | What happens |
|---|---|---|
| Line breakpoint | You know the suspicious line | Execution pauses every time that line is reached |
| Conditional breakpoint | A loop or repeated function only fails for one case | Execution pauses only when the condition is true |
| Logpoint | You need a quick printed clue without editing source | DevTools logs and keeps running |
| Pause on exceptions | An error happens before you know where | Execution pauses where the error is thrown |
Conditional breakpoints are especially beginner-friendly in loops. If a list has 100 items and only item 50 is wrong, pausing 50 times is exhausting. A condition such as i === 50 lets the browser skip straight to the interesting case.
The debugger statement
REAL DEVTOOLSJavaScript has a built-in statement named debugger. When DevTools is open, the browser pauses on that line as if you had placed a breakpoint there. When DevTools is closed, browsers normally ignore it and keep running.
This is useful when a file is hard to find in bundled source, or when you want a temporary pause in a small local experiment. It is also easy to forget. Remove debugger; before committing or sharing code.
function handleDebugClick() { const label = "Debug button"; debugger; console.log(label + " finished");} document.querySelector("button").addEventListener("click", handleDebugClick);Open DevTools first, then click the button inside the framed demo. The code is predefined and harmless; the important line is debugger;.
debugger; before sharing code.The button lives inside a sandboxed iframe with predefined code. If your DevTools were open, the browser paused on the actual debugger; line. If DevTools were closed, nothing special happened and the code continued.
Stepping over, into, and out
STEP THROUGHOnce execution is paused, the toolbar gives you movement buttons. In Chrome and Edge DevTools, Resume is F8, Step over is F10, Step into is F11, and Step out is Shift + F11. Other browsers show similar buttons, but shortcuts can differ.
totalCart(items); // Step into opens totalCartapplyDiscount(total); // Step over runs it and stays herereturn roundPrice(value); // Step out finishes this functionIf a chapter looks trustworthy, you might read its title and keep moving. If it may contain the clue, you open it. If you are already inside and realize it is not the problem, you finish it and return to the caller.
- In real life: Read the chapter title and continue
- In JavaScript: Step over: run the function call without opening it
- In real life: Open the chapter and read each paragraph
- In JavaScript: Step into: enter the called function
- In real life: Finish this chapter and return to the main story
- In JavaScript: Step out: finish the current function and pause at its caller
Where the analogy stops: A book waits forever while you choose. Real pages may have timers, events, or network work happening elsewhere, so pause only as long as you need.
The next replay uses one real buggy program at two granularities. The cart should total 30 and apply a 10 percent discount once, so the right answer is 27. The bug applies the discount twice. Switch between Step into and Step over and notice that the final result is the same real value: 24.3.
Step into opens each helper call. Watch the second applyDiscount call reveal the double-discount bug.
script
function roundPrice(amount) { return Math.round(amount * 100) / 100;} function applyDiscount(amount) { const discounted = amount * 0.9; return roundPrice(discounted);} function totalCart(prices) { let total = 0; for (const price of prices) { total = total + price; } const firstPass = applyDiscount(total); const finalTotal = applyDiscount(firstPass); return finalTotal;} console.log(totalCart(items));The bug is line 18: applyDiscount(firstPass). Step into makes that obvious because you see amount become 27 inside the second helper call. Step over still runs the helper, but it hides the helper’s internal lines and shows only the returned value. Step out would matter if you were already inside roundPrice and wanted to finish that helper and return to applyDiscount.
Watch expressions and the Scope panel
SIMULATORPausing is useful because the debugger shows what names mean at that exact moment. The Scope panel usually groups values into sections. Local means variables in the current function. Closure means variables remembered from an outer function; the Closures lesson goes deeper later. Global or Script means top-level values.
A Watch expression is an expression you pin to the debugger, like a gauge on a dashboard. Every time execution pauses, the browser tries to evaluate it using the current values. Good watches are small: subtotal * discountRate, items.length, or total === 27.
When the program pauses, the current function can reach some drawers easily and others only through outer links. The Scope panel is DevTools showing those reachable drawers.
- In real life: Top drawer right in front of you
- In JavaScript: Local: names in the current function
- In real life: A drawer the function brought from home
- In JavaScript: Closure: remembered outer names
- In real life: The big shared filing cabinet
- In JavaScript: Global/Script: top-level names
Where the analogy stops: Real scope rules are precise and lexical. The drawer picture helps you look in the right place, but later lessons explain exactly how JavaScript decides which drawer a name comes from.
const subtotal = 30;const discountRate = 0.1; function applyDiscount(amount) { const discounted = amount - amount * discountRate; return Math.round(discounted * 100) / 100;} const total = applyDiscount(subtotal);Scope · Local
3027Scope · Global/Script
300.1Watch
327Local values belong to applyDiscount. Global values were created outside the function. The Watch panel recomputes expressions from the paused values.
Watch expressions should be safe to run repeatedly. Avoid expressions that change the page or update data while you are learning. A watch such as cart.add() would change the cart every time the debugger evaluates it.
Which tool fits?
INTERACTIVEDebugging is faster when you pick the smallest tool that answers your question. Sort each scenario by the kind of debugger tool you would reach for first.
- Stop every time a suspicious assignment runs
- A loop fails only when
i === 50 - Print
totalfrom production-like code without editing the file - Leave a quick note while writing a tiny local experiment
- Keep checking
subtotal * discountRateon every pause - See the current function's
price,total, and nearby variables - The code throws before you know which line caused it
- A library catches the error before the console shows the real source
Drop each scenario into the best family of tools. The explanation names the specific tool, such as line breakpoint, conditional breakpoint, logpoint, Watch, console.log, or pause on exceptions.
Where you will use this
Browser debugging is most useful when the code is correct-looking but the values are not. A shopping cart, form validator, quiz score, or button state can all fail because one value changed earlier than you expected.
const items = [20, 10]; function roundPrice(amount) { return Math.round(amount * 100) / 100;} function applyDiscount(amount) { const discounted = amount * 0.9; return roundPrice(discounted);} function totalCart(prices) { let total = 0; for (const price of prices) { total = total + price; } const firstPass = applyDiscount(total); const finalTotal = applyDiscount(firstPass); return finalTotal;} console.log(totalCart(items));A practical debugging routine looks like this:
- Describe the wrong behavior in one sentence.
- Set a breakpoint before the value first looks suspicious.
- Step over trusted helpers; step into helpers that may hide the bug.
- Read Scope before guessing. Add Watch expressions for values you keep recomputing.
- Fix the smallest line, then rerun from the start.
Common misconceptions
“Step over means skip this function.”
Step over still runs the function. It only skips opening that function’s body in the debugger view.
“The Scope panel shows what variables will be later.”
Scope is a snapshot of now: the paused line and the values reachable at that moment.
“A watch expression is just text.”
DevTools evaluates it. Keep watches side-effect free so inspecting does not change the program.
“The course replay is DevTools.”
The replay is recorded from real functions to teach the path. The real browser debugger is in DevTools.
“debugger; is harmless to leave in code.”
It is temporary. It can interrupt developers who open DevTools and makes shared code look unfinished.
| Pair | Use this when | Not this |
|---|---|---|
| Breakpoint vs console.log | You need to pause and inspect several values | You only need a quick printed clue |
| Step over vs Step into | You trust the helper and want its result | You need to inspect the helper line by line |
| Scope vs Watch | You want all nearby variables | You want one pinned expression every pause |
Practice: pause, inspect, decide
5 EXERCISESImagine a breakpoint on return result;. What value would result have in the Scope panel?
function double(number) {
const result = number * 2;
return result;
}
console.log(double(6));When paused on return result;, the Local scope would show number as 6 and result as 12. The program later prints 12.
A loop fails only on item 50. What condition would you type so the browser pauses only when i is 50?
i === 50Right-click or control-click the line number, choose a conditional breakpoint, and enter i === 50. The loop keeps running until that expression is true.
Add a temporary pause with debugger;, test it with DevTools open, and then remove it. Which statement must not be left behind?
function checkForm() {
debugger;
return true;
}Open DevTools, run the code, and the browser pauses on debugger;. After you finish inspecting, delete that line so shared code does not pause unexpectedly.
If execution pauses after line 3, what would a Watch expression for subtotal * discountRate show?
const subtotal = 30;
const discountRate = 0.1;
const total = subtotal - subtotal * discountRate;
console.log(total);subtotal * discountRate is 30 * 0.1, so the Watch panel shows 3. The final total is 27 because the code subtracts that discount from the subtotal.
In the cart replay, which line applies the discount for the second time?
const firstPass = applyDiscount(total);
return firstPass;Line 17 correctly applies the discount once. Line 18 calls applyDiscount(firstPass), so 27 becomes 24.3. Remove the second discount and return the first discounted value.
Quiz: check your understanding
7 QUESTIONSPick an answer, read the explanation, and connect it back to the debugging move you would make in DevTools.
Question 1 of 7What is a breakpoint?
Choose an answer to see the explanation.
Question 2 of 7When does a
debugger;statement pause?Choose an answer to see the explanation.
Question 3 of 7A line calls
applyDiscount(total). You trust the helper and want to stay in the current function. Which button fits?Choose an answer to see the explanation.
Question 4 of 7What does the double-discount program print?
Read the code, then predictfunction discount(amount) { return amount * 0.9; } const first = discount(30); const second = discount(first); console.log(second);Choose an answer to see the explanation.
Question 5 of 7Which Scope section normally shows variables created inside the currently paused function?
Choose an answer to see the explanation.
Question 6 of 7Which tool prints a message at a line without pausing or editing the source file?
Choose an answer to see the explanation.
Question 7 of 7What does the loop print?
Read the code, then predictlet total = 0; for (let i = 1; i <= 3; i = i + 1) { total = total + i; } console.log(total);Choose an answer to see the explanation.
Key takeaways
- Breakpoints pause code so you can inspect the exact moment a value changes.
debugger;is a real temporary pause when DevTools is open; remove it afterward.- Step over runs a call without opening it. Step into opens it. Step out finishes the current function.
- Scope shows reachable values now; Watch pins expressions you want to recalculate on each pause.
- Use the course replay for learning, and your browser’s DevTools for real debugging.
Remember the one-liner.
Browser debugging means pausing real JavaScript, moving one step at a time, and checking values before you guess.
Up next: Coding style & naming.