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.
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 8002Pause 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.
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.
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
Fix the average
In the first example, predict the logs after changing
<=to<. Run it: there should be three step lines andAverage: 80. - 2
Pause for real
Open DevTools (
F12), then Run the second example. When it pauses, findtotalin the Scope panel. PressF8once and checktotalagain. Then fix the bug withNumber(item.price)and predict the new total. - 3
Make the pause conditional
In the second example, change
debugger;toif (typeof item.price !== "number") debugger;. With DevTools open, how many times does it pause now? - 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
These logs show what a breakpoint would show you in DevTools: at `i = 3` the score is `undefined`. Change `<=` to `<` to fix it.
Preview
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`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
Console
Nothing logged yet.Expected console output
Total: 015120
Price types: [ 'string', 'number' ]Preview
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.
Break #2
Leave a debugger statement in
Ship a page with debugger; inside a click handler.
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, andmonitorEvents(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
.thencallback or after anawait, 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
errorandunhandledrejectionevents, Topic 13.2) and upload source maps to your reporting service, so production traces point at your real code.
Remember this
- 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
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 statementdebugger;in your code does the same thing when DevTools is open, and nothing at all when it's closed. - 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
totaloritems.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
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
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 === 999in a long loop. A logpoint (right-click → Add logpoint) prints a message like"i", i, totalto 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 asclick, and on fetch/XHR requests. - 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
Describe a methodical way to debug a function that returns the wrong value.
What is the difference between step over, step into and step out?
What are conditional breakpoints and logpoints, and when would you use each?
When would you use the debugger statement instead of clicking a line number?
Practice
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; }
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.logis 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.