Debugging JavaScript with the Console and DevTools

Find and fix bugs faster: read error messages, use console methods beyond log, set breakpoints, step through code, watch variables and inspect network requests in DevTools.

Lesson 47 of 56Intermediate17 min read

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

  1. Reproduce it. Find the exact steps that make the bug happen every time.
  2. Read the error. Note the error type, the message, and the file and line number.
  3. Form a guess. "I think total is a string, not a number."
  4. Test the guess. Log the value or pause at a breakpoint and look.
  5. Fix one thing, then test again. Changing five things at once hides which one mattered.
  6. 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

ErrorUsually meansExample cause
ReferenceError: x is not definedA name doesn't exist hereTypo, or using a variable outside its scope
TypeError: Cannot read properties of undefined (reading 'name')Something before .name is undefinedMissing data, wrong index, element not found
TypeError: x is not a functionYou called something that isn't a functionTypo in a method name, or a value overwritten
SyntaxError: Unexpected tokenThe code can't be parsedMissing bracket, comma or quote
RangeError: Maximum call stack size exceededEndless recursionNo 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

MethodUse
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.

ControlDoes
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 open

JavaScript 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.group and console.time make 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.

Example 1

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 });
ConsoleEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Line 3A sneaky bug: one price is a string.
  2. Line 7Curly braces label the value in the output.
  3. Line 8table shows all products at a glance. Open your browser's console to see it as a grid.
  4. Lines 10 to 15Grouped checks: a warning for sold-out items and an assertion that catches the string price.
  5. Lines 17 to 19Measure how long a step takes.
  6. Line 20The total is "8243" instead of 35, because adding a string joins text.
Example 2

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(); });
ConsoleEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Lines 1 to 7A helper that runs code and prints the error type and message.
  2. Line 11A misspelt variable gives a ReferenceError.
  3. Line 12Reading a property of null gives the famous "Cannot read properties of null".
  4. Line 13A misspelt method is "not a function".
  5. Line 14JSON needs double quotes around keys and strings.
  6. Line 15A function that calls itself forever overflows the call stack.
Example 3

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);
ConsoleEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Line 5Check for the impossible value only where it matters.
  2. Line 6console.error stands out in red and records the stack trace.
  3. Line 7With DevTools open, execution pauses here so you can inspect item, i and discount.
  4. 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

Don't
console.log(cart); // DevTools shows the object as it is when you expand it, not when it was logged
cart.items.push("extra");
Do
console.log(structuredClone(cart)); // a snapshot of this moment
// or
console.log(JSON.stringify(cart));

Ignoring the first error

Don't
// Fixing the tenth red error in the console first
Do
// Fix the FIRST error: later errors are often caused by it

One early failure, such as an element not found, often triggers a chain of follow-on errors.

Leaving debugging code in production

Don't
debugger;
console.log("HERE!!!", data);
Do
// Remove debugger statements and stray logs before shipping;
// linters like ESLint can flag them automatically

JavaScript 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.

Fill in the blankseasy

Show an array of objects as a table in the console.

const people = [{ name: "Ana", age: 31 }, { name: "Ben", age: 27 }];
console.(people);
Predict the outputmedium

What does this error message tell you?

// TypeError: Cannot read properties of undefined (reading 'length')
console.log(order.items.length);
Multiple choiceeasy

Which statement pauses code when DevTools is open?

Multiple choicemedium

Which stepping control runs the current line without entering the functions it calls?

Fix the bugmedium

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]));
ConsoleEdit the code, then press Run or Ctrl+Enter
Checks
  • Handles strings and missing values
  • Plain numbers still work
  • Empty list gives 0

JavaScript Debugging Quiz

5 questions to check what you learned.

  1. 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
  2. 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
  3. What does a stack trace show?

    • The CSS that applies
    • The chain of function calls that led to the error
    • Network requests
    • Memory usage
  4. Where do you look when fetched data doesn't arrive?

    • The Elements panel
    • The Styles panel
    • The Application panel
    • The Network panel
  5. 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:

  1. Logs `${label}:` followed by the value, using console.log
  2. Returns the value unchanged, so it can be dropped into the middle of an expression
  3. If the value is undefined, null or NaN, also call console.warn with `${label} is ${value}`
function inspect(label, value) {

}

const price = inspect("price", Number("12x"));
const total = inspect("total", price * 2);
ConsoleEdit the code, then press Run or Ctrl+Enter
Checks
  • Returns the value unchanged
  • Logs the label and value
  • Warns about bad values