Command Palette

Search for a command to run...

Hectal
PHASE 13Intermediate ~22 min· topic 4 of 6

Topic 13.4

Debugging with DevTools

In one line

Debugging means finding why code does something different from what you expected; DevTools lets you pause the running code with breakpoints or the debugger statement, step through it one line at a time, and watch every value, instead of guessing.

Think of it like this

A football match on video replay. Watching live, a foul happens too fast to see. The referee's video replay can pause at any moment, step forward frame by frame, and zoom in on one player. DevTools is a replay for your code: it can pause at a chosen line, move forward one line at a time, and show you the value of every variable at that exact moment.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Bug
A mistake that makes code do something different from what you meant.
Debugger
The part of DevTools that can pause running code and show its values.
Breakpoint
A marker on a line that pauses the code there, before that line runs.
debugger statement
debugger; in your code: pauses there when DevTools is open, does nothing otherwise.
Step over / into / out
Run the next line; go inside the function it calls; finish the current function and go back to its caller.
Scope panel
The list of every variable the paused line can see, with its current value.
Watch expression
An expression you type into DevTools that it re-calculates each time the code pauses.
Logpoint
A breakpoint that prints a message instead of pausing, so you don't edit the code.
Conditional breakpoint
A breakpoint that pauses only when an expression you give is true.

Step by step

01Reproduce the bug

This function should return the average of the scores, but it returns NaN. First make the failure happen every time with one small input. Here, average([80, 90, 70]) should be 80.

average.jswhole filejavascript
function average(scores) {
  let total = 0;
  for (let i = 0; i <= scores.length; i++) {
    total += scores[i];
  }
  return total / scores.length;
}

console.log(average([80, 90, 70])); // NaN, expected 80

02Pause inside the loop

Open the file in Sources and click the line number of total += scores[i];. Run the code again. It pauses on that line, and the Scope panel shows i: 0, total: 0 and scores: (3) [80, 90, 70].

Add total and scores[i] to the Watch panel so you see them on every pause.

03Step and watch the values

Press Resume (F8) to run to the next pause. The watched values go 80, 170, 240... then i becomes 3, scores[3] is undefined, and 240 + undefined gives NaN. Found it: the loop runs one time too many.

Step and watch the valuesdiagram
Rendering diagram…

04Use the debugger statement

Sometimes clicking a line is awkward: the code is generated, or it runs only once at page load. Write debugger; where you want to stop, open DevTools, and reload. Combine it with an if to pause only in the interesting case.

Remove every debugger; before you ship. ESLint's no-debugger rule warns about leftovers.

average.jswhole filejavascript
for (let i = 0; i <= scores.length; i++) {
  if (scores[i] === undefined) debugger; // pause only when it goes wrong
  total += scores[i];
}

05Use logpoints instead of editing code

Right-click the line number → Add logpoint and type "i", i, "total", total. Every time the line runs, the console prints those values, and your file stays untouched. This is ideal on a live site, where you can't edit the code.

06Pause where an error is thrown

Turn on Pause on uncaught exceptions (and optionally caught ones) in the Breakpoints sidebar. When an error is thrown, DevTools stops at that exact line, with every variable available. This is usually faster than reading the stack trace alone.

Try it yourself

  1. 1

    Fix the average

    In the first example, predict the logs after changing <= to <. Run it: there should be three step lines and Average: 80.

  2. 2

    Pause for real

    Open DevTools (F12), then Run the second example. When it pauses, find total in the Scope panel. Press F8 once and check total again. Then fix the bug with Number(item.price) and predict the new total.

  3. 3

    Make the pause conditional

    In the second example, change debugger; to if (typeof item.price !== "number") debugger;. With DevTools open, how many times does it pause now?

  4. 4

    Try a logpoint on any site

    On any page you're building, open Sources, right-click a line number inside an event handler and choose Add logpoint. Type a variable name, trigger the event, and watch the console. Remove it from the Breakpoints list when done.

Code & diagrams

Finding an off-by-one bug by printing each step New tab

These logs show what a breakpoint would show you in DevTools: at `i = 3` the score is `undefined`. Change `<=` to `<` to fix it.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

i = 0 | score = 80 | total = 80
i = 1 | score = 90 | total = 170
i = 2 | score = 70 | total = 240
i = 3 | score = undefined | total = NaN
Average: NaN
A debugger statement in the loop New tab

`0 + "15"` joins text and gives `"015"`, then `"015" + 120` gives `"015120"`. The fix is `total + Number(item.price)`. With DevTools closed, `debugger;` does nothing.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Total: 015120
Price types: [ 'string', 'number' ]
Peeking into a chain without breaking it New tab

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

paid: [ { id: 1, total: 250, paid: true }, { id: 3, total: 400, paid: true } ]
over 100: [ { id: 1, total: 250, paid: true }, { id: 3, total: 400, paid: true } ]
ids: [ 1, 3 ]
Result: [ 1, 3 ]

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Log an object, then change it

In browser DevTools, run const cart = { items: 1 }; console.log(cart); cart.items = 5; and then expand the logged object.

terminal
$ DevTools console
── what you'll see ──
The preview shows {items: 1}, but the expanded object shows items: 5.

Break #2

Leave a debugger statement in

Ship a page with debugger; inside a click handler.

terminal
$ the page shows
── what you'll see ──
For users with DevTools open, every click freezes the page in the debugger. Everyone else sees nothing odd.

Myth vs fact

Myth

Real developers debug only with console.log.

Fact

Logging is useful and fast, but breakpoints show every variable at once, the call stack and closures, without editing or reloading. Professionals use both.

Myth

The debugger can only be used on your own local files.

Fact

You can set breakpoints and logpoints on any page's scripts, including live sites. With source maps, minified code maps back to the original files.

Myth

Stepping into a line always goes into my own code.

Fact

It also steps into libraries. Add library files to the Ignore list (right-click the file → Add script to ignore list) and DevTools skips them.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Chrome DevTools can edit code while paused: change a line in Sources, save with Ctrl+S, and the function is replaced live (the current frame restarts). Local overrides keep your edits across reloads, handy for testing a fix on a live site.

  • ▸

    In the Console you can call debug(fnName) to pause whenever that function is called, and monitorEvents(element, "click") to log every event on an element. These helpers only exist in the DevTools console, not in page code.

  • ▸

    Async stack traces: when paused inside a .then callback or after an await, the Call Stack panel shows the frames from before the async gap. This works because DevTools records where each task was scheduled.

  • ▸

    For bugs that only appear for users, add error reporting (the window error and unhandledrejection events, Topic 13.2) and upload source maps to your reporting service, so production traces point at your real code.

Remember this

  1. 1

    A bug is code that does something other than what you meant. Debugging is the method for finding it: (1) reproduce the problem reliably, (2) form a guess about where it goes wrong, (3) check the guess by looking at real values, (4) fix it, and (5) check the fix. console.log (Topic 0.3) is one way to look at values. The debugger in DevTools is another: it shows every value at once without editing your code.

  2. 2

    A breakpoint is a marker that says "pause here". In Chrome or Edge, open DevTools (F12), go to the Sources panel (Firefox calls it Debugger), open your file and click a line number. A blue marker appears. The next time the code reaches that line it pauses before running it. The page freezes, and DevTools shows the paused line highlighted. Writing the statement debugger; in your code does the same thing when DevTools is open, and nothing at all when it's closed.

  3. 3

    While paused, three side panels tell you everything. Scope lists every variable the paused line can see: local ones, ones from closures (Topic 3.6), and globals. Call Stack shows the chain of function calls that led here, like a live stack trace (Topic 13.1); click a lower frame to see that function's variables. Watch holds expressions you type, such as total or items.length, and re-evaluates them every time the code pauses. You can also hover over any variable in the code to see its value, and type in the Console to run code in the paused function's scope.

  4. 4

    Stepping moves the paused code forward under your control. Resume (F8) runs until the next breakpoint. Step over (F10) runs the current line, including any function it calls, and pauses on the next line. Step into (F11) goes inside the function the line calls. Step out (Shift+F11) finishes the current function and pauses back in its caller. Watching variables change step by step is the fastest way to find the exact line where a value goes wrong.

  5. 5

    Special breakpoints save time. A conditional breakpoint (right-click a line number → Add conditional breakpoint) pauses only when an expression is true, such as i === 999 in a long loop. A logpoint (right-click → Add logpoint) prints a message like "i", i, total to the console every time the line runs, without pausing and without editing your file. Pause on exceptions (the octagon pause icon in Sources) stops at the line that throws, even if it's caught. In the Breakpoints sidebar you can also pause on DOM changes (Elements panel → right-click → Break on), on event listeners such as click, and on fetch/XHR requests.

  6. 6

    Debugging is more than tools. Change one thing at a time. Make the failing case as small as possible (one item, not a thousand). If something worked yesterday, find the change that broke it. Explaining the code line by line to someone (or to a rubber duck, a well-known trick called rubber duck debugging) often reveals the wrong assumption. Once fixed, write a test (Topic 13.6) so the same bug can't come back silently.

Explain it without notes

01

Describe a methodical way to debug a function that returns the wrong value.

02

What is the difference between step over, step into and step out?

03

What are conditional breakpoints and logpoints, and when would you use each?

04

When would you use the debugger statement instead of clicking a line number?

Practice

01

This function should return the largest number, but largest([-5, -2, -9]) returns 0. Explain what a breakpoint would show and fix it: function largest(nums) { let max = 0; for (const n of nums) if (n > max) max = n; return max; }

02

Add a line to the second live example's loop that pauses the debugger only when item.price is a string, and fix the total so it prints Total: 135.

Trade-offs

  • ↔

    console.log is quick and shows a history of values over time, but you must guess where to put it and remember to remove it; breakpoints show everything at one moment without editing code, but pausing can change timing-dependent behaviour such as animations or race conditions.

Done when you can

  • I can set a breakpoint in the Sources panel and read the Scope panel.

  • I can use step over, step into, step out and resume.

  • I can add a watch expression, a conditional breakpoint and a logpoint.

  • I can use debugger; and I remove it before shipping.

  • I can pause on exceptions to stop at the line that throws.

  • I debug methodically: reproduce, guess, check values, fix, test.