Debugging JavaScript
console power tools, breakpoints, the debugger statement, stack traces and debugging Node.
Every developer spends a large part of their time finding out why code doesn't do what they expected. Debugging is a skill you can learn: reproduce the problem, read the error, form a hypothesis, inspect the actual values, and fix the cause rather than the symptom. This lesson covers the tools — the console, breakpoints and DevTools in the browser and in Node.js — and a process to use them.
Step 1: read the error message#
The message tells you exactly what happened: something was undefined when we tried to read .reduce on it — so cart.items is undefined. The stack trace below it (in the console or terminal) gives the file and line, plus each function call that led there. Read it top-down; the first line from your code (not a library) is usually where to look.
The console, beyond console.log#
Live objects: browser consoles show a reference to logged objects. If the object changes after logging, expanding it later shows the new values. To log a snapshot, use
console.log(structuredClone(obj))orJSON.stringify(obj, null, 2).
Breakpoints: pause and look around#
Logging means guessing where to look and re-running. A breakpoint pauses the program so you can inspect everything at that moment.
In Chrome/Edge DevTools (Firefox is very similar):
- Open DevTools (
F12) → Sources tab (Firefox: Debugger). - Open your file (
Ctrl+P/Cmd+Pto search). - Click a line number to set a breakpoint.
- Trigger the code (reload, click the button…). Execution pauses on that line.
While paused you can:
- Hover variables to see their values, or check the Scope panel (local, closure and global variables).
- Look at the Call Stack to see how you got here — click a frame to inspect its variables.
- Type expressions in the Console; they run in the paused scope.
- Add Watch expressions that update as you step.
Then control execution:
Smarter breakpoints
- Conditional breakpoint: right-click a line number → Add conditional breakpoint →
item.price === undefined. It only pauses when the condition is true — ideal inside loops. - Logpoint: right-click → Add logpoint →
"item", item. Logs without editing your code. - DOM breakpoints: in the Elements tab, right-click a node → Break on → subtree modifications / attribute modifications / node removal. Finds the code that changes an element.
- Event listener breakpoints: Sources → Event Listener Breakpoints → e.g. Mouse → click.
- Pause on exceptions: the ⏸ button with the stop sign pauses whenever an error is thrown — even caught ones if you tick the option.
- XHR/fetch breakpoints: pause when a request URL contains a given string.
The debugger statement
Handy when it's hard to find the line in DevTools (e.g. bundled code). Never commit it — linters flag it.
Other DevTools panels you'll use#
- Network: every request with status, headers, payload and response, plus timing. The first stop for "the data isn't showing" bugs. Throttle to Slow 4G to test loading states.
- Elements: inspect and edit the live DOM and CSS; see which event listeners are attached.
- Application/Storage: inspect localStorage, sessionStorage, cookies, IndexedDB and service workers.
- Performance: record what the page does over time to find slow functions and layout thrashing.
- Memory: heap snapshots to track down leaks.
- Lighthouse: audits for performance, accessibility and SEO.
Source maps#
Production code is bundled and minified, so stack traces point to index-a1b2c3.js:1:48211. Source maps (.map files generated by Vite, esbuild, TypeScript…) let DevTools show and break on your original source files. Vite enables them in development automatically; enable build.sourcemap if you need them in production.
Debugging Node.js#
Run with the inspector
Then open chrome://inspect in Chrome and click inspect — you get the same Sources panel and breakpoints for Node code. --inspect-brk pauses on the first line; --inspect starts running immediately.
VS Code
VS Code has a built-in JavaScript debugger. Open the Run and Debug panel and choose Node.js, or open a JavaScript Debug Terminal — any node or npm run command started there is debugged automatically, and breakpoints set in the editor just work.
Watch mode and useful flags
A debugging process that works#
- Reproduce it reliably. Write down the exact steps. A bug you can't reproduce, you can't confirm fixed.
- Read the error and stack trace carefully.
- Form a hypothesis: "I think
itemsis undefined because the API returnsdata.items". - Inspect actual values with a breakpoint or log at the point you suspect — and check your assumptions about types (
typeof,Array.isArray). - Narrow it down. Comment out half the code, or use
git bisectto find the commit that broke it. Binary search is fast. - Fix the cause, not the symptom. Adding
?.everywhere silences an error; finding out why the value is missing fixes the bug. - Add a test so it can't come back.
Explaining the problem out loud — to a colleague or a rubber duck — often reveals the answer before you finish the sentence.
Classic bugs to check first#
Common mistakes#
- Changing code randomly until it works, without understanding why.
- Leaving
console.loganddebuggerstatements in committed code — use a linter rule to catch them. - Debugging minified code without source maps.
- Ignoring warnings in the console — they often predict tomorrow's errors.
What's next#
Next: Node.js and npm — running JavaScript outside the browser, using built-in modules, installing packages and writing npm scripts.
Check your understanding
Quick quiz
1.What does the
debugger;statement do when DevTools is open?2.Which console method shows an array of objects as a sortable table?
3.You logged an object, expanded it later in the console, and it shows values that changed *after* the log. Why?
Finished reading?
Mark this lesson complete to track your progress.