JavaScriptBeginner

JavaScript Debugging and Browser DevTools: A Practical Guide

Learn to debug JavaScript with console methods, breakpoints, the debugger statement, call stack, watch, Network and DOM inspection in browser DevTools.

All JavaScript lessons

What you will learn

Every developer, from beginner to expert, spends a large part of the day fixing bugs. The difference is that experienced developers have a method and they know their tools. In this lesson you will learn how to read error messages, how to use the console properly (not just console.log), how to pause your code with breakpoints and the debugger statement, how to walk through code step by step, how to use the Call Stack, Scope and Watch panels, and how to use the Network, Elements and Application panels to find problems in real websites.

What is debugging?

A bug is a mistake in code that makes it behave in the wrong way. Debugging is the process of finding the cause of the bug and fixing it. Your main tool is the browser DevTools, which are built into Chrome, Edge, Firefox, Safari and other browsers.

Opening DevTools

Action Windows / Linux Mac
Open DevTools F12 or Ctrl + Shift + I Cmd + Option + I
Open the Console directly Ctrl + Shift + J Cmd + Option + J
Inspect an element Right click, then Inspect Right click, then Inspect
Command menu (search any tool) Ctrl + Shift + P Cmd + Shift + P

(In Firefox the Console shortcut is Ctrl + Shift + K.)

The panels you will use most (names are from Chrome, other browsers are similar):

Panel Use it to
Elements Inspect and change the HTML and CSS live
Console See messages and errors, and run JavaScript
Sources (Debugger in Firefox) Read your code, set breakpoints, step through it
Network See every request: files, API calls, errors, speed
Application (Storage in Firefox) Look at localStorage, sessionStorage, cookies
Performance and Lighthouse Find what makes the page slow

Step 1: Read the error message

Most bugs announce themselves in the Console as a red error. Learn to read it:

Uncaught TypeError: Cannot read properties of undefined (reading 'name')
    at showUser (app.js:12:18)
    at init (app.js:25:3)
Part Meaning
Uncaught Nobody handled this error with try...catch
TypeError The kind of error (you learned the types in the error handling lesson)
Cannot read properties of undefined (reading 'name') You wrote something.name, but something is undefined
at showUser (app.js:12:18) The error happened in function showUser, file app.js, line 12, column 18
at init (app.js:25:3) showUser was called from init at line 25 (this list is the stack trace)

Click the file name in the Console and DevTools takes you straight to that line. Start looking there, and then look at the functions below it in the stack trace.

The most common error messages

Message Usual cause
x is not defined Typo in a name, or the variable is out of scope
Cannot read properties of undefined (reading 'y') The thing before .y is undefined (wrong variable, data not loaded yet, wrong index)
x is not a function You called something that is not a function (wrong name, or an object instead of a method)
Unexpected token A syntax mistake: missing bracket, comma or quote
Assignment to constant variable You changed a const
Failed to fetch / CORS error Network problem or the server does not allow your site

Step 2: Use the Console well

console.log is great, but the Console has many more tools.

Different kinds of messages

console.log("Normal message");
console.info("Information");
console.warn("Warning: something looks wrong");
console.error("Error: something failed");

Warnings are yellow and errors are red, so they are easy to find in a long output. Use the filter buttons in the Console to show only one kind.

Log several values with labels

Do not log a bare variable. Add a label so you know what you are looking at:

const user = { name: "Riya" };
const age = 22;

console.log("user:", user, "age:", age);
console.log({ user, age });   // trick: an object with names as keys

The second line prints { user: {...}, age: 22 }, labelled automatically.

Format placeholders

console.log("Hello %s, you are %d years old", "Riya", 22);
// %s = string, %d = number, %o = object

console.log("%cStyled text", "color: white; background: purple; padding: 4px;");
// %c applies CSS to the text after it

console.table: arrays and objects as a table

const products = [
  { name: "Pen", price: 10, stock: 100 },
  { name: "Book", price: 120, stock: 15 },
  { name: "Bag", price: 800, stock: 0 }
];

console.table(products);

You get a neat table with a column for each key. Click a column header to sort it.

console.group: organise long output

console.group("Order #101");
console.log("Item: Pen");
console.log("Qty: 2");
console.group("Payment");
console.log("Method: UPI");
console.groupEnd();
console.groupEnd();

Use console.groupCollapsed() to start the group closed.

console.time: measure speed

console.time("sum loop");

let sum = 0;
for (let i = 0; i < 1000000; i++) sum += i;

console.timeEnd("sum loop");   // sum loop: 3.2 ms (your time will differ)

The label must be the same in both calls.

console.count: how many times did this run?

function handleClick() {
  console.count("handleClick");
}

handleClick();   // handleClick: 1
handleClick();   // handleClick: 2

console.assert: log only when something is wrong

const total = 130;
console.assert(total === 120, "Total is wrong!", total);
// Prints an error only when the condition is false

console.trace: who called this function?

function inner() {
  console.trace("Where am I called from?");
}

function outer() {
  inner();
}

outer();   // prints the stack trace

console.dir: look inside an object

console.dir(element) shows an HTML element as an object with all its properties, while console.log(element) shows it as HTML.

console.clear()

Clears the Console. Many developers also use the clear button at the top of the Console.

A small warning about console.log and objects

When you log an object, the browser keeps a live link. If you expand it later, you see the values as they are now, not as they were when you logged it. To save a snapshot, copy it:

const cart = { items: 1 };

console.log("snapshot:", structuredClone(cart));

cart.items = 5;   // the snapshot above still shows items: 1

You will meet console again, together with alert, prompt and confirm, in the Input and Output lesson.

Step 3: Pause the code with breakpoints

console.log shows you what happened. A breakpoint lets you stop time and look around. When the code reaches a breakpoint, it pauses before running that line, and you can see every variable at that moment.

Setting a breakpoint

  1. Open DevTools and go to the Sources panel.
  2. Find your file (press Ctrl + P and type the file name).
  3. Click the line number where you want to pause. A blue marker appears.
  4. Run the action that triggers the code (reload the page or click a button).
  5. The code pauses and the line is highlighted.

The debugger statement

You can also write the pause into your code:

function calculateTotal(items) {
  let total = 0;

  for (const item of items) {
    debugger;               // the code pauses here if DevTools is open
    total += item.price;
  }

  return total;
}

If DevTools is closed, debugger does nothing. Always remove it before publishing your site.

Stepping through code

When the code is paused, use the buttons at the top of the debugger (the keyboard shortcuts are for Chrome):

Button Shortcut What it does
Resume F8 Continue running until the next breakpoint
Step over F10 Run this line. If it calls a function, run the whole function and stop on the next line
Step into F11 If this line calls a function, go inside it
Step out Shift + F11 Finish the current function and stop where it was called
Deactivate breakpoints Ctrl + F8 Turn all breakpoints off temporarily

A good routine: step over most lines, and step into only when you suspect the function you are about to call.

What to look at while paused

Section What you can do
Scope See every variable in the local, closure and global scope, with their current values
Watch Add any expression (like total or items.length) and see its value update at every step
Call Stack See the chain of functions that led here. Click a function to jump to it and see its variables
Breakpoints See, turn on and off, or remove all your breakpoints

You can also hover over any variable in the code to see its value, and you can type into the Console while paused. The Console runs in the scope of the paused line, so total or item will work there.

Other kinds of breakpoints

Type How to set it Best for
Conditional breakpoint Right click a line number, then Add conditional breakpoint, and type a condition like i === 99 Stopping on one item in a long loop
Logpoint Right click a line number, then Add logpoint, and type a message Printing a value without editing your code
Event listener breakpoint Sources panel, Event Listener Breakpoints, tick click Finding which code runs on a click
DOM breakpoint Elements panel, right click an element, Break on (subtree, attribute or node removal) Finding which code changes an element
XHR / fetch breakpoint Sources panel, XHR/fetch Breakpoints, add part of a URL Pausing when a request is sent
Pause on exceptions Click the pause icon at the top of the Sources panel Stopping on the exact line that throws an error

Step 4: The Network panel

Many bugs are not in your code but in the data coming from the server.

  1. Open the Network panel and reload the page.
  2. Use the Fetch/XHR filter to see only API calls.
  3. Click a request to see its details:
Tab What it shows
Headers The URL, method (GET, POST), status code, and headers
Payload The data you sent
Preview / Response The data you received (JSON is shown as a tree)
Timing How long each part took

Reading the status codes

Code Meaning First thing to check
200 OK The request worked. Is the data what you expected?
301 / 302 Redirect The URL moved
304 Not modified The browser used its saved copy
400 Bad request The data you sent is wrong
401 / 403 Not logged in / not allowed Token, login or permissions
404 Not found Wrong URL or wrong file path (check spelling and capital letters)
500 Server error The bug is on the server

Useful tricks:

  • Tick Disable cache (while DevTools is open) to always load the newest files.
  • Use the throttling menu (for example Slow 4G or Offline) to test how your site behaves on a slow or broken connection.
  • Right click a request and choose Copy, then Copy as fetch, to repeat the same request in code.

Step 5: Inspect the page (Elements panel)

  • Inspect any part of the page by right-clicking it and choosing Inspect.
  • Change text, attributes and CSS directly. Changes disappear on reload, so they are perfect for experiments.
  • In the Styles tab you can tick styles on and off, see which rule wins and which are crossed out.
  • Use the Computed tab to see the final value of every property, with the box model diagram.
  • Use Event Listeners to see which functions are attached to an element.
  • Use the device toolbar (Ctrl + Shift + M) to test phone and tablet sizes.

The element you selected is available in the Console as $0:

$0.textContent = "Changed from the console!";
$0.style.color = "red";

Handy Console shortcuts

These work only inside the DevTools Console (not in your own files):

Shortcut Meaning
$0 The element currently selected in the Elements panel
$_ The result of the previous command
$("css selector") Same as document.querySelector()
$$("css selector") Same as document.querySelectorAll(), returned as a real array
copy(value) Copies a value (for example, a big JSON) to your clipboard
clear() Clears the Console

Step 6: The Application panel

Open Application to see what your site stores in the browser:

  • Local Storage and Session Storage: see, edit and delete keys. Great for checking the JSON you saved in earlier lessons.
  • Cookies: see names, values and expiry.
  • IndexedDB and Cache Storage: bigger storage used by web apps.

Right click the storage and choose Clear to start fresh while testing.

A step-by-step debugging method

Randomly changing code until it works is slow and risky. Use this method instead:

  1. Reproduce the bug. Find the exact steps that make it happen every time.
  2. Read the error message and the line number. If there is no error, describe what you expected and what happened.
  3. Find where it goes wrong. Add console.log or set a breakpoint, and check the values step by step. Find the first place where a value is not what you expected.
  4. Form a guess about the cause (“I think price is a string”).
  5. Test your guess. Check the value and its type (typeof).
  6. Fix one thing at a time, and run the code again.
  7. Test again with other inputs, so the fix does not break something else.

Tip (rubber duck debugging): explain your code line by line, out loud, to a rubber duck or a friend. Very often you find the bug while you explain.

Real-life use cases

1. “My total is wrong”: a bug story

const items = [
  { name: "Pen", price: "10" },
  { name: "Book", price: "120" }
];

let total = 0;
for (const item of items) {
  total += item.price;
}

console.log("Total:", total);   // "010120" instead of 130

How to find it:

  1. The result is text, not a number. That is a clue.
  2. Add console.log(typeof item.price) inside the loop, or put a breakpoint and watch item.price in the Scope panel.
  3. You see that price is "10" (text), so + joins text instead of adding.
  4. Fix it by converting: total += Number(item.price);.

2. Off-by-one error in a loop

const colors = ["red", "green", "blue"];

for (let i = 0; i <= colors.length; i++) {   // bug: <= should be <
  console.log(i, colors[i]);
}
// The last line prints: 3 undefined

Step through the loop and watch i and colors[i]. You will see colors[3] is undefined, and that i goes one step too far.

3. “Why does the button do nothing?”

Check these in order:

  1. Console: any red error? (A typo in the file can stop the whole script.)
  2. Elements: inspect the button, and in Event Listeners check if a click listener is attached.
  3. Sources: put a breakpoint inside the handler. Does it pause when you click?
  4. Network: if the button calls an API, did the request go out? What status code came back?

4. Finding which code changes an element

Right click the element in Elements, then Break on, then attribute modifications. Now when any script changes it, DevTools pauses on that exact line and the Call Stack shows who did it.

5. Debugging an API call

async function loadUser() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  console.log("status:", response.status, "ok:", response.ok);   // check the status first

  const user = await response.json();
  console.log(user);
}

loadUser();

In the Network panel, click the request, open Preview, and compare the real data with what your code expects. A wrong property name (like user.fullname instead of user.name) is a very common cause of undefined.

6. Log only one item from a long loop

for (let i = 1; i <= 1000; i++) {
  if (i === 777) {
    console.log("at 777:", i);
  }
}

Better: set a conditional breakpoint with i === 777, or a logpoint, so you do not even need to edit the code.

7. Measure which part is slow

console.time("filter");
const result = Array.from({ length: 100000 }, (_, i) => i).filter((n) => n % 2 === 0);
console.timeEnd("filter");

console.log(result.length);   // 50000

For bigger problems, record a profile in the Performance panel.

8. Check saved data

If your app forgets its settings, open Application, then Local Storage, and check if the key exists and if the JSON text is valid. A saved value like "undefined" is a classic sign of a bug.

Common mistakes

  • Using only console.log("here"). Add labels and values: console.log("price:", price, typeof price).
  • Changing many things at once. Change one thing, test, and repeat. Otherwise you will not know what fixed it.
  • Not reading the error message. The answer is often in the first line and the line number.
  • Leaving debugger and console.log in the final code. Remove them before you publish.
  • Trusting the logged object’s current look. Expanded objects show the latest values. Log a copy with structuredClone when you need a snapshot.
  • Editing the page in the Elements panel and expecting it to stay. Changes disappear on reload. Copy useful fixes back into your files.
  • Testing with an old cached file. Use Disable cache in the Network panel, or do a hard reload (Ctrl + Shift + R).
  • Forgetting the Network panel when data is undefined. Check what the server really sent.
  • Ignoring warnings (the yellow messages). They often point to a real bug in the making.
  • Debugging a file that is not the one running. Check the file name in the Sources panel. In bigger projects the code may be bundled.
  • Giving up and guessing. Use the method: reproduce, locate, guess, test, fix.

Practice

  1. Trigger these 3 errors on purpose in the Console, and read each message: an undefined variable, null.name, and JSON.parse("{ bad").

  2. Use console.table to print an array of 3 objects. Sort it by clicking a column header.

  3. Use console.group to print a small order with an item list and a payment section.

  4. Measure the time of a loop with console.time and console.timeEnd.

  5. Put a breakpoint inside a function called in a loop. Step through two loops with Step over, and watch one variable in the Watch panel.

  6. Write a function with the debugger statement. Open DevTools, run it, and look at the Scope and Call Stack panels.

  7. Set a conditional breakpoint that only stops when i === 5 in a loop to 10.

  8. Open the Network panel, reload any website, filter by Fetch/XHR, and find a request with the status 200. Look at its Preview.

  9. Open any page, select a heading with Inspect, and change its text using $0.textContent in the Console.

  10. Open Application, add a key to localStorage from the Console with localStorage.setItem("test", "hello"), and find it in the panel.

  11. Challenge: find the bug in this code without running it first, then confirm with a breakpoint:

    function average(numbers) {
      let sum = 0;
      for (let i = 0; i <= numbers.length; i++) {
        sum += numbers[i];
      }
      return sum / numbers.length;
    }
    
    console.log(average([10, 20, 30]));

Recap

  • Debugging means finding and fixing the cause of a bug. Use a method: reproduce, locate, guess, test, fix, and change one thing at a time.
  • Read the error message. It gives the error type, the reason, and the file and line number. The stack trace shows who called whom.
  • Beyond console.log, use console.table, group, time and timeEnd, count, assert, trace, and dir. Add labels to your logs.
  • Breakpoints (click a line number in Sources) and the debugger statement pause the code. Use Resume (F8), Step over (F10), Step into (F11) and Step out (Shift + F11).
  • While paused, check Scope, Watch and Call Stack, and use the Console. Conditional breakpoints and logpoints save you from editing code.
  • The Network panel shows requests, responses and status codes. The Elements panel lets you inspect and change the DOM and CSS live. The Application panel shows localStorage, sessionStorage and cookies.
  • In the Console, $0, $_, $(), $$() and copy() are handy shortcuts.
  • Remove debugger and extra console.log calls before publishing.
  • Next you will learn Symbols: a special primitive type for unique values and the hidden keys behind features like for...of.