Debugging with DevTools
Every programmer writes bugs, every day. What separates beginners from experienced developers is not fewer bugs but a calm, repeatable way of finding them. Your most important tool is already installed: every modern browser has DevTools, a set of panels for inspecting and debugging the page.
In this lesson you will open DevTools, read error messages properly, use more of the console than console.log(), pause code with breakpoints and step through it line by line, and check network requests. You will practise on two small bugs that beginners hit all the time.
Opening DevTools
Press F12, or Ctrl+Shift+I on Windows and Linux (Cmd+Option+I on a Mac). You can also right-click anything on a page and choose Inspect. DevTools has several panels; these are the ones you will use most:
| Panel | What it is for |
|---|---|
| Elements (Inspector in Firefox) | The live DOM and its CSS, including changes made by JavaScript |
| Console | Messages, errors, and a place to type and run JavaScript |
| Sources (Debugger in Firefox) | Your script files, breakpoints and step-by-step running |
| Network | Every request the page makes, with status, timing and response |
| Application (Storage in Firefox) | localStorage, sessionStorage, cookies and other stored data |
The exact names and layout differ a little between Chrome, Edge, Firefox and Safari, but the ideas are the same everywhere. In Safari, first turn on the developer features in Safari's settings.
Read the error message
When a script fails, the Console shows a red message such as:
Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
at app.js:12:31Beginners often skim past this, but it tells you nearly everything. Read it in three parts:
- The type,
TypeError: you used a value the wrong way. - The message: something before
.addEventListenerwasnull. - The location,
app.js:12:31: file, line and column. Click it to jump to that exact line in the Sources panel.
Now ask "why is that value null?" Here, querySelector found nothing, perhaps because of a typo in the selector or because the script ran before the element existed (see The DOM: Selecting Elements).
These messages (shown as Chrome writes them) cover a large share of beginner bugs:
| Message | Usual cause |
|---|---|
x is not defined | A typo in a variable name, or the variable is out of scope |
Cannot read properties of undefined (reading 'name') | The thing before .name is undefined: a wrong key, or data not loaded yet |
x.getName is not a function | The method name is misspelt, or the object is not what you think |
Assignment to constant variable. | You assigned a new value to a const |
missing ) after argument list, Unexpected token | A bracket, quote or comma is missing or extra |
Debugging with the console
The quickest technique is to print values at key points and compare them with what you expected. Run this example. The average should be about 85, but it prints NaN:
function average(list) {
let total = 0;
for (let i = 0; i <= list.length; i++) {
console.log(`i = ${i}, value = ${list[i]}, total so far = ${total}`);
total += list[i];
}
return total / list.length;
}
console.log(`Ananya's average: ${average([72, 88, 95])}`);i = 0, value = 72, total so far = 0 i = 1, value = 88, total so far = 72 i = 2, value = 95, total so far = 160 i = 3, value = undefined, total so far = 255 Ananya's average: NaN
The log shows the loop running a fourth time with i = 3, where list[3] is undefined, and 255 + undefined is NaN. The condition should be i < list.length. This is called an off-by-one error, one of the most common bugs of all. Notice the log message includes labels: i = 3 is far more useful than a bare 3.
The console has more than log:
console.table([{ name: "Aarav", marks: 78 }, { name: "Isha", marks: 91 }]); // a neat table
console.error("Payment failed"); // red, with a stack trace
console.warn("Stock is low"); // yellow warning
console.count("render"); // "render: 1", "render: 2"... counts calls
console.time("load"); // start a stopwatch...
console.timeEnd("load"); // ...and print how long it took
console.assert(total >= 0, "Total should never be negative"); // logs only if false
console.group("Cart"); // indent the following messages...
console.groupEnd(); // ...until hereIn the Console panel you can also type any expression and press Enter to run it on the live page. In Chrome and Firefox, $0 refers to the element currently selected in the Elements panel.
Pausing with breakpoints
Adding logs means editing, saving and reloading. A breakpoint is often faster: it pauses the code at a line so you can look at every variable at that moment.
To set one, open the Sources panel, open your file and click the line number. Or write the statement debugger; in your code: when DevTools is open, the browser pauses there. While paused you can:
- Hover over any variable to see its value.
- Read the Scope section, which lists all local and outer variables.
- Add Watch expressions, such as
total + 1, that update as you step. - Read the Call Stack, which shows how the code got here.
Then move through the code with the step buttons (the shortcuts below are Chrome's):
| Button | Shortcut | What it does |
|---|---|---|
| Resume | F8 | Run until the next breakpoint |
| Step over | F10 | Run the current line, without going inside function calls |
| Step into | F11 | Go inside the function called on this line |
| Step out | Shift+F11 | Finish the current function and stop in its caller |
Try it on this page. It should add two prices, but it shows a strange total:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Buggy bill</title>
</head>
<body>
<h1>Meera's Boutique, Lucknow</h1>
<p><label>Kurta ₹ <input type="number" value="499"></label></p>
<p><label>Dupatta ₹ <input type="number" value="120"></label></p>
<button type="button" id="calc">Calculate total</button>
<p id="total"></p>
<script>
document.querySelector("#calc").addEventListener("click", () => {
const inputs = document.querySelectorAll("input");
let total = 0;
for (const input of inputs) {
debugger; // pauses here, but only while DevTools is open
total += input.value;
}
document.querySelector("#total").textContent = `Total: ₹${total}`;
});
</script>
</body>
</html>Without DevTools open, the page shows "Total: ₹0499120". Open DevTools and click the button again. The code pauses at debugger. Hover over input.value: it is "499", a string with quotes. Press F10 to step once, and total becomes "0499". The + joined text instead of adding numbers. The fix is total += Number(input.value); (or input.valueAsNumber), as you learned in Working with Forms. Remove the debugger line once the bug is fixed.
You can also pause on events: Event Listener Breakpoints in the Sources panel pause when, for example, any click handler starts. That helps when you cannot tell which code is running.
Checking network requests
When data does not appear, open the Network panel and repeat the action. Click the request to see its status (200, 404, 500...), the request headers and payload you sent, and the response the server returned. Many "JavaScript bugs" turn out to be a wrong URL, a 404, or a server returning an HTML error page instead of JSON. Turn on Preserve log to keep requests across page reloads, and use the throttling menu to test on a slow connection. See Fetch API.
A method that works
- Reproduce the bug reliably. Note the exact steps.
- Read the error message and its location.
- Shrink the problem: remove code until you have the smallest example that still fails.
- Guess a cause, then check it with a log or a breakpoint. Do not change code based on a guess alone.
- Fix one thing at a time and test again.
Explaining the code aloud, line by line, to a friend or even a rubber duck on your desk, is surprisingly effective. Saying what each line should do often reveals the line that does not.
Common mistakes
- Ignoring the console. Keep DevTools open while you develop, and read every red message.
- Changing many things at once. If the bug disappears, you will not know which change fixed it.
- Assuming instead of checking. "That variable must be a number" is exactly the kind of belief that hides bugs. Look at the value.
- Leaving
debuggeror noisy logs in finished code. Remove them before you publish.
Key points
- Open DevTools with F12 or Ctrl+Shift+I (Cmd+Option+I on a Mac).
- Read every error in three parts: type, message, and the file and line link.
- Use labelled
console.logcalls and other console methods to compare actual values with expected ones. - Breakpoints and
debugger;pause the code so you can inspect variables and step line by line. - Check the Network panel for status codes and responses when data goes missing.
- Reproduce, read, shrink, check, then fix one thing at a time.
Exercise
This function should return the total runs for a batting card, but it returns "0452311". Use console.log (or a breakpoint) to find out why, then fix it.
function totalRuns(scores) {
let total = 0;
for (const score of scores) {
total += score.runs;
}
return total;
}
console.log(totalRuns([{ runs: "45" }, { runs: "23" }, { runs: "11" }]));Show solution
Logging typeof score.runs inside the loop prints string three times: the runs were stored as text, so += joins them. Convert each value before adding:
function totalRuns(scores) {
let total = 0;
for (const score of scores) {
total += Number(score.runs);
}
return total;
}
console.log(totalRuns([{ runs: "45" }, { runs: "23" }, { runs: "11" }])); // 79Data that comes from forms, storage or some APIs is often text, so check its type when maths gives strange results.
Quick Quiz
1. An error says at app.js:27:5. What does 27:5 tell you?
The location shows the file, line and column, and clicking it opens that exact spot.
2. What does the debugger; statement do?
debugger; acts like a breakpoint written in code; without DevTools open, it does nothing.
3. While paused, which action runs the current line without going inside the functions it calls?
Step over runs the whole line as one step; step into enters the called function.
4. A list should show data from an API but stays empty. Where should you look first?
The Network panel shows whether the request succeeded and what the server actually returned.
Learn JavaScript with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
