Every developer writes bugs, every day. What separates beginners from experienced developers isn't how few bugs they write, it's how quickly they find them. Debugging is a skill, and like any skill it has techniques you can learn.
The first technique is reading error messages properly. They tell you what went wrong and exactly where, but beginners often skim past them. The second is using the console well: not just console.log, but tables, groups, timers and warnings that make output easy to scan. The third, and most powerful, is the debugger built into every browser's DevTools: you can pause your code on any line, look at every variable, and run the program one step at a time to watch exactly where things go wrong.
In this lesson you'll learn a reliable debugging process, decode the most common JavaScript errors, use the console methods professionals rely on, and get comfortable with breakpoints, stepping and the Network tab. You learned to throw and catch errors in JavaScript errors; now you'll learn to hunt them down.
A debugging process that works
- Reproduce it. Find the exact steps that make the bug happen every time.
- Read the error. Note the error type, the message, and the file and line number.
- Form a guess. "I think
totalis a string, not a number." - Test the guess. Log the value or pause at a breakpoint and look.
- Fix one thing, then test again. Changing five things at once hides which one mattered.
- Check nearby code for the same mistake.
When you're stuck, explain the code line by line out loud, even to an object on your desk. This "rubber duck debugging" works surprisingly often, because explaining forces you to check your assumptions.
Reading common errors
| Error | Usually means | Example cause |
|---|---|---|
ReferenceError: x is not defined | A name doesn't exist here | Typo, or using a variable outside its scope |
TypeError: Cannot read properties of undefined (reading 'name') | Something before .name is undefined | Missing data, wrong index, element not found |
TypeError: x is not a function | You called something that isn't a function | Typo in a method name, or a value overwritten |
SyntaxError: Unexpected token | The code can't be parsed | Missing bracket, comma or quote |
RangeError: Maximum call stack size exceeded | Endless recursion | No base case |
The stack trace under an error lists the chain of function calls that led there, newest first. Click the top line in your own code (not a library) to jump to the problem.
The console beyond log
| Method | Use |
|---|---|
console.log({ total, items }) | Log variables with their names, using an object |
console.table(arrayOfObjects) | Show arrays and objects as a sortable table |
console.error(), console.warn() | Red and yellow messages that stand out and include a stack trace |
console.group("label") ... console.groupEnd() | Indent related messages together |
console.time("load") ... console.timeEnd("load") | Measure how long something takes |
console.count("render") | Count how many times a line runs |
console.assert(cond, "message") | Log only when a condition is false |
console.trace() | Print the current call stack |
Wrapping variables in curly braces, console.log({ price }), is the single most useful trick: you see { price: "12" } with its name and quotes, which immediately shows it's a string.
Breakpoints and stepping in DevTools
Open DevTools (F12 or Ctrl+Shift+I), go to the Sources panel, open your file and click a line number to set a breakpoint. When the code reaches it, everything pauses.
| Control | Does |
|---|---|
| Resume (F8) | Run until the next breakpoint |
| Step over (F10) | Run the current line, without going into functions it calls |
| Step into (F11) | Go inside the function called on this line |
| Step out (Shift+F11) | Finish the current function and return to the caller |
While paused, hover any variable to see its value, check the Scope panel for every local variable, add expressions to Watch, and see the Call Stack. You can also type debugger; in your code to pause there whenever DevTools is open, and right-click a line number for a conditional breakpoint that only pauses when, say, i === 99.
JavaScript Debugging Syntax
Debugging tools
console.log({ user, total });
console.table(products);
console.group("Checkout"); console.log("step 1"); console.groupEnd();
console.time("sort"); data.sort(); console.timeEnd("sort");
console.assert(total >= 0, "Total went negative", total);
debugger; // pauses here when DevTools is openJavaScript Debugging Summary
- Debug methodically: reproduce, read the error, guess, test, fix one thing at a time.
- Error type, message and line number point straight to the problem; the stack trace shows how you got there.
console.log({ x }),console.table,console.groupandconsole.timemake output clearer.- Breakpoints pause code so you can inspect variables and step line by line.
- The Network tab shows exactly what each request sent and received.
JavaScript Debugging Examples
Each example runs in the page. Edit the code and press Run to see what changes.
Console methods that make output readable
const products = [
{ name: "Mug", price: 8, stock: 12 },
{ name: "Teapot", price: "24", stock: 0 },
{ name: "Spoon", price: 3, stock: 40 },
];
console.log({ first: products[0] });
console.table(products);
console.group("Stock check");
for (const p of products) {
if (p.stock === 0) console.warn(`${p.name} is sold out`);
console.assert(typeof p.price === "number", `${p.name} price is a ${typeof p.price}`);
}
console.groupEnd();
console.time("total");
const total = products.reduce((sum, p) => sum + p.price, 0);
console.timeEnd("total");
console.log({ total });Line-by-line explanation
- Line 3A sneaky bug: one price is a string.
- Line 7Curly braces label the value in the output.
- Line 8
tableshows all products at a glance. Open your browser's console to see it as a grid. - Lines 10 to 15Grouped checks: a warning for sold-out items and an assertion that catches the string price.
- Lines 17 to 19Measure how long a step takes.
- Line 20The total is "8243" instead of 35, because adding a string joins text.
Decoding real error messages
function tryIt(label, fn) {
try {
fn();
} catch (err) {
console.log(`${label} -> ${err.name}: ${err.message}`);
}
}
const user = { profile: null, tags: ["a"] };
tryIt("typo", () => usr.name);
tryIt("missing data", () => user.profile.name);
tryIt("not a function", () => user.tags.mapp((t) => t));
tryIt("bad JSON", () => JSON.parse("{name: 'Ana'}"));
tryIt("recursion", () => { const f = () => f(); f(); });Line-by-line explanation
- Lines 1 to 7A helper that runs code and prints the error type and message.
- Line 11A misspelt variable gives a ReferenceError.
- Line 12Reading a property of
nullgives the famous "Cannot read properties of null". - Line 13A misspelt method is "not a function".
- Line 14JSON needs double quotes around keys and strings.
- Line 15A function that calls itself forever overflows the call stack.
Using debugger and conditional logging
Open DevTools before running to pause at the debugger line.
function applyDiscounts(items) {
return items.map((item, i) => {
const discount = item.onSale ? 0.2 : 0;
const price = item.price * (1 - discount);
if (Number.isNaN(price)) {
console.error(`Item ${i} has a bad price`, item);
debugger;
}
return { ...item, price };
});
}
const result = applyDiscounts([
{ name: "Lamp", price: 40, onSale: true },
{ name: "Desk", price: undefined, onSale: false },
]);
console.table(result);Line-by-line explanation
- Line 5Check for the impossible value only where it matters.
- Line 6
console.errorstands out in red and records the stack trace. - Line 7With DevTools open, execution pauses here so you can inspect
item,ianddiscount. - Lines 13 to 16The second item is missing its price, which turns into
NaN.
Common JavaScript Debugging Mistakes
Logging an object and reading it later
console.log(cart); // DevTools shows the object as it is when you expand it, not when it was logged
cart.items.push("extra");console.log(structuredClone(cart)); // a snapshot of this moment
// or
console.log(JSON.stringify(cart));Ignoring the first error
// Fixing the tenth red error in the console first// Fix the FIRST error: later errors are often caused by itOne early failure, such as an element not found, often triggers a chain of follow-on errors.
Leaving debugging code in production
debugger;
console.log("HERE!!!", data);// Remove debugger statements and stray logs before shipping;
// linters like ESLint can flag them automaticallyJavaScript Debugging in Real Projects
Debugging on the job
Professional developers spend a large part of their time debugging. Teams use error-tracking services such as Sentry to collect stack traces from real users' browsers. Source maps let DevTools show your original code even after it has been minified. The Performance and Network panels track down slow pages, and React and Vue have DevTools extensions for inspecting components and state.
JavaScript Debugging Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Show an array of objects as a table in the console.
const people = [{ name: "Ana", age: 31 }, { name: "Ben", age: 27 }];
console.(people);
console.table displays rows and columns.What does this error message tell you?
// TypeError: Cannot read properties of undefined (reading 'length')
console.log(order.items.length);Which statement pauses code when DevTools is open?
Which stepping control runs the current line without entering the functions it calls?
average(scores) should return the mean of the numbers, but some scores arrive as strings and one is missing. Convert strings to numbers, skip missing (null or undefined) values, and return 0 for an empty list.
function average(scores) {
let total = 0;
for (const s of scores) total += s;
return total / scores.length;
}
console.log(average([10, "20", null, 30]));- Handles strings and missing values
- Plain numbers still work
- Empty list gives 0
JavaScript Debugging Quiz
5 questions to check what you learned.
What is the first thing to do with an error message?
- Delete the line
- Read its type, message and line number
- Restart the browser
- Add more code
Why log variables as console.log({ price })?
- The output shows the variable's name and its exact value and type
- It's faster
- It hides the value
- It saves the value to a file
What does a stack trace show?
- The CSS that applies
- The chain of function calls that led to the error
- Network requests
- Memory usage
Where do you look when fetched data doesn't arrive?
- The Elements panel
- The Styles panel
- The Application panel
- The Network panel
What is a conditional breakpoint?
- A breakpoint that pauses only when an expression you give is true
- A breakpoint inside an if statement
- A breakpoint that runs once
- A try...catch block
JavaScript Debugging Challenge
Make a safe debug helper
Write inspect(label, value) that helps debugging:
- Logs
`${label}:`followed by the value, usingconsole.log - Returns the value unchanged, so it can be dropped into the middle of an expression
- If the value is
undefined,nullorNaN, also callconsole.warnwith`${label} is ${value}`
function inspect(label, value) {
}
const price = inspect("price", Number("12x"));
const total = inspect("total", price * 2);- Returns the value unchanged
- Logs the label and value
- Warns about bad values