completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Debugging in the browser

Pause JavaScript with browser breakpoints, step through function calls, and inspect values with Scope and Watch panels.

By the end you can
  • 01
    Pause code on purposeUse line breakpoints, conditional breakpoints, and the debugger statement to stop the code where you want.
  • 02
    Move through paused codePick Step over, Step into, Step out, or Resume, depending on what you want to find out.
  • 03
    Look at values while pausedRead the Local, Closure, and Global groups in the Scope panel, and pin Watch expressions.

Debugging means pausing your code

A bug is a mistake in a program. Because of it, the program does something you did not expect. Debugging means finding that mistake and fixing it.

The browser has a tool for this, called the debugger. It can pause your JavaScript while it runs. While the code is paused, you can:

  • see which line runs next,
  • move forward one line at a time,
  • look at the values your variables hold right now.

In earlier lessons you used DevTools, the developer tools built into the browser. You used its console and console.log to print clues. Printing is still useful. But the program keeps running while it prints. The debugger is different. It stops the program, so you can look around before anything else changes.

This page uses a replay, not the real debugger

The step-by-step player in this course is a replay: it plays back code that we ran and recorded earlier. It is not a debugger. This lesson uses the replay to explain each idea. Then you try the real debugger in your own browser's DevTools.

Real-life analogyA breakpoint is the pause button on a movie

Imagine a mystery movie. At full speed, you might miss a clue on a desk. If you pause at the right moment, you can see the clue clearly. The debugger lets you do the same with code: stop at one line, look carefully, then carry on.

In real life: Pause at the exact scene
In JavaScript: A breakpoint pauses the code on one exact line
In real life: Look at the people and things on screen
In JavaScript: Look at the values in the Scope panel
In real life: Move forward one frame
In JavaScript: Step forward to the next line

To find the debugger, open DevTools and pick the right panel:

  • In Chrome and Edge, open the Sources panel.
  • In Firefox, open the Debugger panel.
  • In Safari, first turn on the developer features in Safari's settings. Then open the Sources panel.

The names are different in each browser. The ideas in this lesson are the same in all of them.

Breakpoints: tell the browser where to pause

BREAKPOINTS

A breakpoint is a marker you put on a line of code. When JavaScript reaches that line, the browser pauses before it runs the line. To set one in most browsers, open your file in the debugger panel and click the line number.

Pause before a cart total changesPop out in the code editor (opens in a new tab)JavaScript
const cart = { total: 20 };cart.total = cart.total + 5;console.log(cart.total);

Line 1 makes a cart object with a total of 20. Line 2 adds 5 to the total. Line 3 prints the total.

Put a breakpoint on line 2. The browser pauses before line 2 runs. The Scope panel lists your variables and the values they hold right now. It shows cart.total as 20. Now press Step over, the button that runs one line. Line 2 runs, and cart.total is now 25. Then line 3 prints 25.

The line cart.total = cart.total + 5. Left: paused before line 2, the box cart.total holds 20. An arrow marked step over leads to the right: after line 2, the same box holds 25.cart.total = cart.total + 5;20cart.totalpaused before line 2step over25cart.totalafter line 2
The breakpoint pauses the code before line 2 runs, so cart.total is still 20. Press Step over: line 2 runs, and the same value is now 25.

This is the main question a debugger answers: what value did a line get, and what did it change?

The debugger has several kinds of breakpoints:

  • Line breakpoint: pauses every time one line runs.
  • Conditional breakpoint: pauses only when a test you write is true. For example, i === 50 inside a loop.
  • Logpoint: prints a message when a line runs. It does not pause, and it does not change your file.
  • Pause on exceptions: pauses on the line where an error is thrown. An exception is another word for a thrown error. Many browsers can also pause on errors that your code catches with try/catch.
  • DOM and event listener breakpoints: pause when part of the page changes, or when an event such as a click happens. The DOM is the browser's model of the page. These are browser features. They are not new JavaScript code.
Where each kind of breakpoint goesJavaScript
// 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");

This block is not one program to run. Each comment names one kind of breakpoint, and the line under it shows where it goes. In plain words, line by line:

  1. Line 2 is a line to pause on, with a line breakpoint.
  2. Line 5 is the test you type for a conditional breakpoint.
  3. Line 8 is the message you type for a logpoint.
  4. Line 11 throws an error. Pause on exceptions would stop on this line.

The table below compares four of these tools. Read each row as: "I use this tool when…, and then this happens."

Four ways to pause or print
ToolWhen does it help?What happens?
Line breakpointYou know which line looks wrong.The code pauses every time that line runs.
Conditional breakpointA loop or a function goes wrong in only one case.The code pauses only when your test is true.
LogpointYou want to print a clue without changing your file.DevTools prints a message, and the code keeps running.
Pause on exceptionsAn error happens, but you don't know where.The code pauses on the line that throws the error.

Conditional breakpoints help a lot in loops. Say a list has 100 items, and only item 50 is wrong. A normal breakpoint would pause 50 times before you reach it. A test such as i === 50 lets the browser skip straight to the item you care about.

A condition asks a yes-or-no questionPop out in the code editor (opens in a new tab)JavaScript
const score = 42;console.log(score === 42);

Line 1 stores 42 in score. Line 2 prints the result of score === 42, which is true. So a conditional breakpoint with the test score === 42 would pause here. If you change the score to 41, the test gives false, and the browser keeps running. The test only reads values. It does not change them.

The debugger statement

REAL DEVTOOLS

JavaScript has a built-in statement called debugger. A statement is one instruction in your code. When DevTools is open, the browser pauses on the debugger; line, just like at a breakpoint. When DevTools is closed, browsers usually ignore it and keep running.

It helps in two cases:

  • You can't easily find your file in the debugger panel. Tools that pack your code into one big file can make this hard.
  • You want a quick pause in a small test on your own computer.

It is also easy to forget. Remove debugger; before you share your code.

A short pause in a small functionPop out in the code editor (opens in a new tab)JavaScript
function saveTodo(todo) {  debugger;  console.log(todo + " saved");} saveTodo("tea");

Line 2 pauses the code, but only for someone who has DevTools open. Press Resume, the button that lets the code keep running. Then line 3 prints tea saved. This is handy for a quick check. In code you share, a line breakpoint is usually better, because it leaves nothing behind in the file.

A real debugger statement
The code inside the small pagePop out in the code editor (opens in a new tab)JavaScript
function handleDebugClick() {  const label = "Debug button";  debugger;  console.log(label + " finished");} document.querySelector("button").addEventListener("click", handleDebugClick);
The real small pageOpen DevTools first
Try it yourself

Open DevTools first. Then click the button inside the small page in the frame. Its code is fixed and safe. The line to watch is debugger;.

This is a real browser feature, not the lesson replay. Remove debugger; before you share your code.
What just happened?

The button sits in a small separate page inside this one, called an iframe. Its code is fixed and safe. If your DevTools was open, the browser paused on the real debugger; line. If DevTools was closed, nothing special happened, and the code kept running.

Step over, step into, and step out

STEP THROUGH

When the code is paused, the debugger's toolbar has buttons that move it forward. In Chrome and Edge, they are:

  • Resume (F8): keep running until the next breakpoint.
  • Step over (F10): run the current line, then pause on the next one.
  • Step into (F11): if the line calls a function, go inside that function and pause on its first line.
  • Step out (Shift + F11): finish the function you are in, then pause on the line that called it.

Other browsers have the same buttons, but the keys can be different.

Which button fits each callJavaScript
totalCart(items);          // Step into opens totalCartapplyDiscount(total);    // Step over runs it and stays herereturn roundPrice(value); // Step out finishes this function

This block is not one program to run. Each line shows one call, and its comment names a button. In plain words:

  1. Step into goes inside totalCart, so you can read its lines.
  2. Step over runs applyDiscount and pauses on the next line, without going inside.
  3. Step out finishes the function you are in, and goes back to the line that called it.
Step over still runs the functionPop out in the code editor (opens in a new tab)JavaScript
function addTax(price) {  return price + 2;} const total = addTax(5);console.log(total);

Lines 1 to 3 make a function, addTax, that adds 2 to a price. Line 5 calls addTax(5) and stores the result in total. Line 6 prints it.

Pause on line 5. Then:

  • Step over runs addTax(5) without showing its lines. It stores 7 in total and pauses on line 6.
  • Step into goes inside addTax first, and pauses on line 2.

Both ways print the same result: 7. The button changes only what you see. It does not change what the code does.

The line const total = addTax(5). Left, Step over: the addTax(5) card stays closed and an arrow gives 7. Right, Step into: a window called addTax opens and shows its line, price + 2. An arrow gives 7 again.const total = addTax(5);Step overaddTax(5)7the call runs, closedyou stay on line 5Step intoaddTaxprice + 27the call opensyou see line 2
Both buttons run addTax(5), and both give 7. Step over keeps the function closed and stays on line 5. Step into opens it, so you can read its line 2.
Real-life analogyStepping is how closely you read a book

If a chapter looks fine, you read its title and move on. If it might hold the clue, you open it and read it. If you are inside a chapter and the clue is not there, you finish it and go back to the main story.

In real life: Read the chapter title and move on
In JavaScript: Step over: run the function call without going inside
In real life: Open the chapter and read each paragraph
In JavaScript: Step into: go inside the function that is called
In real life: Finish the chapter and go back to the main story
In JavaScript: Step out: finish the current function and go back to the line that called it

The next replay runs one program that has a bug. A cart holds two prices, 20 and 10, so the total is 30. The program should take 10 percent off once. That gives 27. But the bug takes 10 percent off twice. Pick Step into or Step over. Both show the same wrong result: 24.3.

One buggy cart, two ways to step
Step 0 of 24Ready
Your turn: follow the blue line

Step into goes inside each function call. Watch the second applyDiscount call show the bug: the discount is taken off twice.

Running in
  1. script
Next: line 1
 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));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose what happens at each function call

Both replays come from the same real run. Only the amount of detail changes.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The bug is on line 18: applyDiscount(firstPass). Step into shows it clearly. Inside the second call to applyDiscount, you can see that amount is already 27. With Step over, the function still runs, but you see only the value it gives back, not its lines.

Step out helps when you are already inside a function. For example, if you are inside roundPrice, Step out finishes roundPrice and takes you back to applyDiscount, the function that called it.

The Scope panel and Watch expressions

SIMULATOR

When the code is paused, the debugger shows what each name holds at that moment. The Scope panel lists the variables your code can reach right now. It puts them in groups:

  • Local: variables inside the function that is running now.
  • Closure: variables from an outer function that the current function still remembers. The Closures lesson explains this later.
  • Global or Script: variables made at the top level of the page or script, outside any function.

One detail: a top-level let does not become a property of window, the object that stands for the browser window. DevTools may still list it under Script.

An expression is a piece of code that gives a value, like 2 + 3. A Watch expression is an expression you pin to the debugger, like a gauge on a car's dashboard. Each time the code pauses, the browser works out its value again from the current values. Good watches are small, like subtotal * discountRate, items.length, or total === 27.

A safe Watch expressionPop out in the code editor (opens in a new tab)JavaScript
const subtotal = 30;const discountRate = 0.1;console.log(subtotal * discountRate);

Line 1 stores a subtotal of 30. Line 2 stores a discount rate of 0.1, which means 10 percent. Line 3 prints subtotal * discountRate, which is 3.

Pause after line 2 and pin subtotal * discountRate in Watch. It shows 3, the same as the console. This watch only reads values, so it is safe to work out again and again. A watch such as cart.add() is different. It would add to the cart every time DevTools works it out.

Left, safe: the Watch subtotal * discountRate only reads values and shows 3 at every pause. Right, risky: the Watch cart.add() changes the cart, so it holds one more item after every pause.safe: only readssubtotal * discountRate3the same 3 every pauserisky: changes thingscart.add()items: 4one more item every pause
DevTools runs every Watch again at each pause. subtotal * discountRate only reads, so it always shows 3. cart.add() changes the cart, so each pause adds one more item.
Real-life analogyScope is a set of drawers

Picture a desk with drawers. The drawer in front of you is the easiest to reach. Another drawer came with you from home. A big cabinet in the corner is shared by everyone. When the code pauses, the Scope panel opens every drawer the current function can reach, and shows you what is inside.

In real life: The drawer right in front of you
In JavaScript: Local: names in the current function
In real life: A drawer the function brought with it from home
In JavaScript: Closure: names remembered from an outer function
In real life: The big filing cabinet everyone shares
In JavaScript: Global/Script: top-level names

Start with one small calculation. In the replay, pause inside calculateTotal to see its Local values. When the function finishes, its Local values are gone. The top-level total stays.

Step through Local values and the total that comes back
Step 0 of 9Ready
Your turn: follow the blue line

Follow this calculation. See which values are Local during the call, and which stay at the top level of the script afterward.

Running in
  1. script
Next: line 6
function calculateTotal(amount, discountRate) {  const discount = amount * discountRate;  return amount - discount;} const discountRate = 0.1;const total = calculateTotal(subtotal, discountRate);console.log(total);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Line 2 makes the Local value discount. A Watch for amount * discountRate would show 3 at that pause. Line 8 stores the 27 that the function gives back in total, at the top level of the script. That is why the next pause no longer shows the function's Local values.

Left: paused on line 2, inside calculateTotal. The Local cart holds discount: 3. Right: paused after the call. The Script cart holds total: 27, and the Local values are gone.paused on line 2discount: 3Localinside calculateTotalafter the calltotal: 27ScriptLocal is gone
While calculateTotal runs, Scope shows its Local values, like discount. After it finishes, those values are gone. Only the top-level total is left.
A pretend debugger panel: Scope and Watch
The code being looked atPop out in the code editor (opens in a new tab)JavaScript
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);
Paused on line 6Paused before applyDiscount gives back its result

Scope · Local

amount30
discounted27

Scope · Global/Script

subtotal30
discountRate0.1

Watch

amount * discountRate3
Math.round(discounted * 100) / 10027
Step 1 of 2Paused before applyDiscount gives back its result

The Local values belong to applyDiscount. The Global values were made outside the function. Watch works out each expression again from the paused values.

This is a pretend debugger panel, not DevTools. The code and the Watch values come from real functions in this lesson.
Keep watches safe

DevTools works out every Watch expression again at each pause. So pick watches that only read values. Avoid watches that change the page or your data. A watch such as cart.add() would add to the cart each time it runs.

Which tool fits?

INTERACTIVE

Debugging is faster when you pick the smallest tool that answers your question. First, here is what to do each time the code pauses.

What to do when the code pauses
  1. Read the highlighted line before you change anything.
  2. Check Scope. Compare the value you expected with the value you got.
  3. Read the call stack if you need to know which function called this one. The call stack is the list of functions that are running, newest first.
  4. Press Resume when this pause has answered your question. The code runs on to the next breakpoint.

Now sort each case below by the kind of tool you would use first.

Which debugging tool fits?
  • Pause every time one line that looks wrong runs
  • A loop goes wrong only when i === 50
  • Print total from a file you don't want to change
  • Print a quick note while you write a tiny test on your own computer
  • See subtotal * discountRate at every pause
  • See the current function's price, total, and other nearby variables
  • The code throws an error, and you don't know which line caused it
  • Some other code catches the error, so the console never shows where it started
Try it yourself
0 of 8 correct

Put each case in the group of tools that fits it best. Each explanation names the exact tool: a line breakpoint, a conditional breakpoint, a logpoint, Watch, console.log, or Pause on exceptions.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you will use this

The debugger helps most when the code looks right, but the values are wrong. A shopping cart, a form check, a quiz score, or a button can all go wrong because one value changed earlier than you thought.

Find why a form cannot submitPop out in the code editor (opens in a new tab)JavaScript
const form = { email: "" };const canSubmit = form.email !== "";console.log(canSubmit);

Line 1 makes a form with an empty email. Line 2 sets canSubmit to true only if the email is not empty. Line 3 prints the result.

Say the form's Submit button won't work. Put a breakpoint on line 2 instead of guessing. Scope shows form.email as an empty string, "". So canSubmit becomes false, and line 3 prints false.

Here is the full cart program from the replay again. Lines 17 and 18 are highlighted. Try the routine below on it.

The cart code with the bugPop out in the code editor (opens in a new tab)JavaScript
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 good debugging routine has five steps:

  1. Say what goes wrong, in one sentence.
  2. Set a breakpoint just before the value first looks wrong.
  3. Step over functions you trust. Step into functions that might hide the bug.
  4. Read Scope before you guess. Add a Watch for any value you keep working out by hand.
  5. Fix the smallest line you can. Then run the program again from the start.

Common mix-ups

“Step over means skip this function.”

Step over still runs the function. It only stops you from seeing the function's lines in the debugger.

“The Scope panel shows what variables will be later.”

Scope shows values right now: on the paused line, at that moment.

“A watch expression is just text.”

DevTools runs it as code. So keep watches safe: they should only read values, never change them.

“The course replay is DevTools.”

The replay plays back real functions, so you can learn the steps. The real debugger is in your browser's DevTools.

“debugger; is harmless to leave in code.”

It is only for a short time. It pauses anyone who opens DevTools, and it makes shared code look unfinished.

Each row of the table below compares two tools that are easy to mix up. Read it as: "Use the first tool when…, and the second tool when…"

Two similar tools, two different jobs
Two toolsUse the first one whenUse the second one when
Breakpoint or console.logYou need to pause and look at several values.You only need one quick printed clue.
Step over or Step intoYou trust the function and only want its result.You need to read the function line by line.
Scope or WatchYou want to see all the nearby variables.You want one pinned expression at every pause.

Practice: pause, inspect, decide

5 EXERCISES

Try each exercise before you open the hints. Some ask what a value would be at a pause. Others ask you to use the real debugger in your own browser.

Exercise 1 · Warm-upPredict a paused value

Imagine a breakpoint on return result;. What value would the Scope panel show for result?

Starter codePop out in the code editor (opens in a new tab)JavaScript
function double(number) {
  const result = number * 2;
  return result;
}
console.log(double(6));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeSet a conditional breakpoint

    A loop goes wrong only on item 50. What test would you type so the browser pauses only when i is 50?

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeUse debugger, then remove it

      Add a short pause with debugger;. Try it with DevTools open, then remove it. Which statement must you not leave behind?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeWatch an expression

        Say the code pauses after line 3. What would a Watch expression for subtotal * discountRate show?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const subtotal = 30;
        const discountRate = 0.1;
        const total = subtotal - subtotal * discountRate;
        console.log(total);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeFind the double-discount bug

          In the cart program, which line takes the discount off for the second time?

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Pick an answer and read why it is right or wrong. Then think about which DevTools button or panel you would use.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What is a breakpoint?

              Choose an answer to see the explanation.

            2. Question 2 of 7When does a debugger; statement pause?

              Choose an answer to see the explanation.

            3. Question 3 of 7A line calls applyDiscount(total). You trust that function, and you want to stay in the current one. Which button fits?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does the double-discount program print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function discount(amount) {
                return amount * 0.9;
              }
              const first = discount(30);
              const second = discount(first);
              console.log(second);

              Choose an answer to see the explanation.

            5. Question 5 of 7Which Scope group usually shows the variables made inside the function that is paused?

              Choose an answer to see the explanation.

            6. Question 6 of 7Which tool prints a message at a line, without pausing and without changing your file?

              Choose an answer to see the explanation.

            7. Question 7 of 7What does the loop print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              let 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

            • A breakpoint pauses the code, so you can see the exact moment a value changes.
            • debugger; pauses the code when DevTools is open. Remove it when you are done.
            • Step over runs a function call without going inside. Step into goes inside. Step out finishes the current function.
            • Scope shows the values your code can reach right now. Watch shows the expressions you pinned, worked out again at every pause.
            • The course replay helps you learn. Your browser's DevTools debugs real code.

            Remember the one-liner.
            Pause the code, move one line at a time, and check the values before you guess.

            Up next: Coding style & naming.

            CompleteFrontend Clear concepts. Working examples.