π Debugging with Chrome DevTools
Chrome DevTools is X-ray vision for your web app. Instead of scattering console.log everywhere and guessing, you can pause your code mid-run, inspect every variable, and step through execution one line at a time. Learning to drive DevTools well is one of the highest-leverage skills a developer can build β it turns hours of head-scratching into minutes.
Week 2 · Day 5 (Friday: Error Handling and Debugging) · Lecture 2
π― Learning Objectives
By the end of this lesson, you will be able to:
- Open DevTools and identify the purpose of each major panel
- Use Console methods beyond
logβtable,group,time,assert,trace - Set line, conditional, and logpoint breakpoints in the Sources panel
- Step through paused code with Step Over / Into / Out and read the Call Stack & Scope
- Add Watch expressions and use the
debuggerstatement - Debug asynchronous flows and inspect requests in the Network panel
Estimated Time: 55 minutes
Practice: Use breakpoints to find two planted bugs in a shopping-cart script.
In This Lesson
Your Debugging Toolkit
Just as a doctor uses imaging to diagnose what's happening inside a patient, DevTools lets you look inside a running application β examine the DOM, watch network traffic, profile performance, and, most importantly for us today, pause and inspect JavaScript as it executes.
Every experienced developer relies on a debugger. The console.log habit works for tiny checks, but it forces you to guess in advance what to log and re-run after every change. A breakpoint flips that around: stop the code where it matters, then explore everything in scope, live.
π Note on screenshots
This lesson describes DevTools by feature and keyboard shortcut rather than embedding screenshots, because the interface changes with every Chrome release. Open a real page (even this one) alongside the lesson and try each step as you read β muscle memory is the goal.
Opening DevTools & the Panels
There are three quick ways in:
- F12 (Windows/Linux) or Cmd+Opt+I (Mac)
- Right-click any element β Inspect
- Ctrl+Shift+J (Win/Linux) or Cmd+Opt+J (Mac) opens straight to the Console
DevTools is organized into panels, each a specialized tool. For JavaScript debugging you'll live mostly in Console and Sources.
DOM & CSS] A --> C[Console
logs & REPL] A --> D[Sources
debugger & breakpoints] A --> E[Network
requests & responses] A --> F[Performance
profiling] A --> G[Memory
heap snapshots] A --> H[Application
storage & cookies]
We'll focus on the two highlighted workflows: experimenting and reading errors in the Console, and pausing execution in Sources.
The Console Panel
The Console is both an error-reporting center and a live JavaScript playground β you can type any expression and run it against the current page. Beyond console.log, there's a whole family of purpose-built methods.
// Severity levels β filterable in the console toolbar
console.log("Plain message");
console.info("Informational");
console.warn("Warning β yellow");
console.error("Error β red, with a stack trace");
// Styled output with %c
console.log("%cBig blue banner", "color: #3b82f6; font-size: 20px;");
// Placeholders: %s string, %d number, %o object
console.log("User %s has %d points", "Alice", 150);
// Inspect data structures nicely
const users = [
{ name: "Alice", role: "admin" },
{ name: "Bob", role: "editor" }
];
console.table(users); // renders a sortable table
console.dir(users[0]); // shows object properties, not just a preview
Grouping, timing, counting, tracing
// Collapsible groups keep related logs tidy
console.group("Checkout");
console.log("Validating cartβ¦");
console.log("Charging cardβ¦");
console.groupEnd();
// Measure how long something takes
console.time("render");
renderTable();
console.timeEnd("render"); // "render: 12.4ms"
// Count how many times a line runs
function onScroll() {
console.count("onScroll fired"); // onScroll fired: 1, 2, 3β¦
}
// Assert only logs when the condition is FALSE
console.assert(cart.total >= 0, "Total went negative!");
// Show how execution reached this point
function inner() { console.trace("How did we get here?"); }
π‘ Handy console-only helpers
$_β the value of the last evaluated expression$0β the element currently selected in the Elements panel ($1β¦$4are previous selections)$(sel)β shorthand fordocument.querySelector(sel)$$(sel)β shorthand fordocument.querySelectorAll(sel)(returns a real array)clear()β empty the console
The Sources Panel & Breakpoints
The Sources panel is where real debugging happens. It has three regions: the file navigator (left), the code editor (center), and the debugger sidebar (right, with Call Stack, Scope, Watch, and Breakpoints).
Setting a breakpoint
Open a source file, then click a line number to set a breakpoint. When execution reaches that line it pauses before running it, and everything in scope becomes inspectable.
function calculateDiscount(price, discountPercent) {
// π Click this line's number to pause here every time
const discount = price * (discountPercent / 100);
const finalPrice = price - discount;
return { originalPrice: price, discount, finalPrice };
}
Four kinds of breakpoint
| Type | How to set it | Best for |
|---|---|---|
| Line | Click a line number | Pausing every time a line runs |
| Conditional | Right-click a line β "Add conditional breakpoint", enter an expression | Pausing only when e.g. order.total > 1000 |
| Logpoint | Right-click a line β "Add logpoint" | Logging a value without editing code or pausing |
| Event listener | Sources β "Event Listener Breakpoints" β tick e.g. Mouse β click | Pausing whenever a specific event fires |
The debugger statement
You can also drop a breakpoint from code itself. When DevTools is open, execution pauses on this line exactly as a manual breakpoint would; when it's closed, the line does nothing.
function processOrder(order) {
debugger; // pauses here when DevTools is open
if (order.total > 1000) applyDiscount(order);
}
β οΈ Don't ship debugger
A stray debugger statement will freeze the page for any user who has DevTools open. Remove them before committing β most linters and build tools can flag or strip them automatically.
Exception breakpoints
Toggle "Pause on uncaught exceptions" (and optionally "Pause on caught exceptions") in the Sources sidebar. DevTools then stops at the exact line an error is thrown β the fastest way to find the source of a mysterious failure.
Stepping, Call Stack & Scope
Once paused, you control execution with the stepping buttons at the top of the debugger sidebar:
As you step, the Scope pane shows every variable currently visible β Local, Closure, and Global β with live values. The Call Stack pane shows how you got here; click any frame to jump to that function's context.
function outer() {
const outerVar = "from outer";
function middle() {
const middleVar = "from middle";
function inner() {
const innerVar = "from inner";
debugger;
// Paused here, the Scope pane shows:
// Local: innerVar
// Closure: middleVar, outerVar
// Global: window, ...
// The Call Stack shows: inner β middle β outer β (anonymous)
console.log(outerVar, middleVar, innerVar);
}
inner();
}
middle();
}
outer();
Watch expressions
The Watch pane evaluates expressions you add and refreshes them at every step β perfect for tracking a computed value as a loop runs.
function shoppingCart(items) {
let total = 0;
let count = 0;
// Add to Watch: total count total / count
for (const item of items) {
total += item.price * item.quantity;
count += item.quantity;
}
return { total, count, average: count ? total / count : 0 };
}
Async & Network Debugging
Modern DevTools follows asynchronous code across await and .then() boundaries, so the Call Stack shows the full logical chain β not just the microtask that happened to run.
async function loadDashboard() {
try {
console.group("Loading dashboard");
const user = await fetchUser(); // step through each await
const perms = await fetchPermissions(user.id);
const data = await fetchDashboard(user.id, perms);
console.groupEnd();
renderDashboard(data);
} catch (error) {
// "Pause on exceptions" stops right here
console.error("Dashboard failed:", error);
}
}
The Network panel
When the bug isn't in your logic but in the data you received, the Network panel is home. It lists every request with its method, status, size, and timing. Click a request to inspect its Headers, Payload, Preview, and Response tabs.
- Filter by type (Fetch/XHR, JS, CSS, Imgβ¦) to cut the noise
- Check the status code first β a red 404 or 500 explains a lot
- Use the throttling dropdown to simulate "Slow 3G" and catch loading bugs
- Right-click a request β "Copy as fetch" to reproduce it in the Console
async function fetchData() {
// This request shows up in the Network panel with full detail
const response = await fetch("/api/data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: "test" })
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
Tips & Tricks
- Command Menu: Ctrl/Cmd+Shift+P runs any DevTools action by name β a fuzzy search for the whole toolset.
- Ignore list: right-click a framework file in Sources β "Add script to ignore list" so stepping skips library internals and stays in your code.
- Live editing: edit a file in Sources and press Ctrl/Cmd+S to try a fix without leaving the browser.
- Snippets: save reusable debugging scripts under Sources β Snippets and run them on any page.
- Coverage: the Coverage tool highlights unused JavaScript and CSS.
- Device mode: emulate phones and tablets to debug responsive layouts.
Practice & Quiz
ποΈ Exercise 1: Find the two cart bugs
Goal: This cart should end with a total of 5 after removing the book, but it doesn't. Open DevTools, set breakpoints in addItem and removeItem, and watch this.total to find two bugs.
function ShoppingCart() {
this.items = [];
this.total = 0;
}
ShoppingCart.prototype.addItem = function (item) {
this.items.push(item);
this.total += item.price; // π ignores quantity
};
ShoppingCart.prototype.removeItem = function (id) {
const index = this.items.findIndex(i => i.id === id);
if (index > 0) { // π should be >= 0
const item = this.items[index];
this.total -= item.price * item.quantity;
this.items.splice(index, 1);
}
};
const cart = new ShoppingCart();
cart.addItem({ id: 1, name: "Book", price: 20, quantity: 2 });
cart.addItem({ id: 2, name: "Pen", price: 5, quantity: 1 });
cart.removeItem(1);
console.log("Total:", cart.total); // want 5
π‘ Hint
Watch this.total after each addItem. Then in removeItem, add a Watch on index β for the book, index is 0, which the > 0 check wrongly rejects.
β Solution
ShoppingCart.prototype.addItem = function (item) {
this.items.push(item);
this.total += item.price * item.quantity; // β
include quantity
};
ShoppingCart.prototype.removeItem = function (id) {
const index = this.items.findIndex(i => i.id === id);
if (index >= 0) { // β
index 0 is valid
const item = this.items[index];
this.total -= item.price * item.quantity;
this.items.splice(index, 1);
}
};
// Now: Book adds 40, Pen adds 5 β 45; remove Book (-40) β 5 β
ποΈ Exercise 2: A conditional breakpoint hunt
Goal: A loop processes 10,000 records and one of them corrupts a running sum. Rather than stepping 10,000 times, describe how a conditional breakpoint finds the culprit instantly.
β Solution
Set a breakpoint inside the loop, right-click it, choose "Add conditional breakpoint", and enter a condition that only the bad case satisfies β e.g. Number.isNaN(record.price) or runningTotal < 0. Execution runs full-speed and pauses only on the offending record, where you can read its values in the Scope pane.
π― Quick Quiz
Question 1: Which stepping action runs the current line but does not descend into a function called on it?
Question 2: You want to log a value without pausing and without editing the source file. What do you use?
Question 3: Which panel shows a request's status code, headers, and response body?
Best Practices & Pitfalls
β Do
- Reach for a breakpoint before scattering
console.logβ it's faster and shows everything - Use conditional breakpoints to skip straight to the interesting case
- Turn on "Pause on exceptions" when hunting the source of a thrown error
- Add framework/library files to the ignore list so stepping stays in your code
- Read the full error in the Console β the stack trace names the exact line
β Don't
- Commit
debuggerstatements or noisyconsole.logs to production - Assume the bug is in your code before checking the Network panel's status codes
- Ignore warnings (yellow) β they often predict the error (red) you're about to hit
- Debug minified bundles without source maps β enable them so you see real names
β A repeatable debugging loop
Reproduce β read the error & stack β set a breakpoint just before it β inspect the Scope β form a hypothesis β test a fix β verify. Consistency beats cleverness.
Summary
π Key Takeaways
- DevTools opens with F12; Console and Sources are your JS-debugging home
- The Console offers far more than
log:table,group,time,assert,trace, and shortcuts like$0and$$() - Breakpoints pause code so you can inspect live values β line, conditional, logpoint, and event-listener variants each shine in different cases
- Step through with Over / Into / Out; read the Call Stack and Scope; track values with Watch
- DevTools follows async chains, and the Network panel diagnoses data problems your logic can't
π Additional Resources
- Chrome DevTools β official documentation
- Chrome β Debug JavaScript
- MDN β console API reference
π What's Next?
Now that you can pause and inspect any code, let's build a mental catalogue of the failures you'll see most. Next: Common JavaScript Errors and Solutions β how to recognize, understand, and fix each one on sight.
π X-ray vision unlocked!
You'll never debug blind again. Pause, inspect, and let the code tell you what it's really doing.