Skip to main content

πŸ” 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 debugger statement
  • 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.

graph TD A[Chrome DevTools] --> B[Elements
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…$4 are previous selections)
  • $(sel) β€” shorthand for document.querySelector(sel)
  • $$(sel) β€” shorthand for document.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

TypeHow to set itBest for
LineClick a line numberPausing every time a line runs
ConditionalRight-click a line β†’ "Add conditional breakpoint", enter an expressionPausing only when e.g. order.total > 1000
LogpointRight-click a line β†’ "Add logpoint"Logging a value without editing code or pausing
Event listenerSources β†’ "Event Listener Breakpoints" β†’ tick e.g. Mouse β†’ clickPausing 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:

graph LR A[Resume F8] --> B[Run until the next breakpoint] C[Step Over F10] --> D[Run this line, don't dive into calls] E[Step Into F11] --> F[Enter the function on this line] G[Step Out Shift+F11] --> H[Finish this function, pause at caller]

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 debugger statements or noisy console.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 $0 and $$()
  • 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

πŸš€ 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.