-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
117: Debugging with Breakpoints and Watch Expressions
Imagine you're a film director on a high-budget movie set. You're filming a complex action sequence, but something looks "off" in the playback. You can't just watch the whole scene at normal speed and hope to spot the mistake—it's too fast. Instead, you shout "Cut!" and freeze the action. You walk onto the set, move the actors an inch to the left, check if the prop gun is actually loaded with blanks, and then tell everyone to resume from exactly that frame.
Debugging with breakpoints is exactly that "Cut!" moment for your code. Instead of letting your program run from start to finish and guessing what happened based on the final output, you're freezing time. The "checking the props" part is where Watch Expressions come in—it's like having a magnifying glass pointed at one specific actor, so you don't have to scan the whole set to see if they're blinking at the wrong time.
Stopping Time in Your Code
Most of us start by peppering our code with console.log(). I did it for years. But honestly? It's a mess. You end up with a console full of noise, and you have to keep adding and removing logs just to track a single variable. Breakpoints are a cleaner way to do this.
Let's look at a real scenario. Say you're building a shopping cart, and for some reason, the final total is coming back as NaN. You've got a loop calculating prices, and somewhere, something is breaking.
function calculateOrderTotal(cart, taxRate) {
let subtotal = 0;
for (let i = 0; i < cart.length; i++) {
const item = cart[i];
// Imagine a bug here: one item is missing a price property
subtotal += item.price * item.quantity;
}
const tax = subtotal * taxRate;
return subtotal + tax;
}
const myCart = [
{ name: "Mechanical Keyboard", price: 150, quantity: 1 },
{ name: "USB-C Cable", price: 20, quantity: 2 },
{ name: "Desk Mat", quantity: 1 } // Oops, price is missing!
];
console.log(calculateOrderTotal(myCart, 0.08));
If you set a breakpoint on the line subtotal += item.price * item.quantity;, the browser will pause execution right there. You can then hover your mouse over item to see exactly what's inside it for that specific iteration of the loop. On the third pass, you'd see price: undefined, and you'd immediately know why the math is failing.
Keeping an Eye on the Suspects
Breakpoints are great for stopping, but Watch Expressions are for monitoring. When you're in the browser's Sources tab, there's a "Watch" pane. Instead of hovering over variables or digging through the entire "Scope" list, you can explicitly tell the debugger: "I don't care about anything else; just keep showing me the value of subtotal."
I find this incredibly useful when you're stepping through a loop. As you click the "Step Over" button to move to the next line, you can watch the subtotal value change in real-time. The moment it flips from a number to NaN, you've found your culprit. It transforms the process from "guessing where the bug is" to "watching the bug happen."
Navigating the Pause
Once you've hit a breakpoint, you have a few steering wheels at your disposal. You'll see a few icons in the debugger panel that I use every single day:
- Step Over: This moves you to the next line. If the current line is a function call, it just runs the function and moves to the next line in the current file. It's the "just get me to the next line" button.
- Step Into: This is for when you see a function call and think, "Wait, the bug might be inside that function." It dives deep into the function's definition.
- Step Out: If you've stepped into a function and realized everything is fine there, this jumps you back out to the caller.
It takes a bit of practice to stop relying on the console, but once you get the hang of the debugger, you'll feel like you have superpowers. You're no longer reading a post-mortem report of what your code did; you're performing a live autopsy.
📋 Practical Task
Fixing the Broken Inventory Ledger
You've been handed a piece of code for an inventory system that is supposed to calculate the total value of a warehouse. However, the final result is incorrect, and the developer who wrote it left no logs behind. Your goal is to use breakpoints and watch expressions to find the logic error.
The Setup:
const inventory = [
{ item: "Laptop", stock: 10, valuePerUnit: 1000 },
{ item: "Mouse", stock: 50, valuePerUnit: 25 },
{ item: "Monitor", stock: 20, valuePerUnit: 200 },
{ item: "HDMI Cable", stock: 100, valuePerUnit: "15" }, // Note the type here
{ item: "Webcam", stock: 15, valuePerUnit: 80 }
];
function calculateWarehouseValue(items) {
let totalValue = 0;
for (let i = 0; i < items.length; i++) {
const product = items[i];
// BUG: The calculation here is causing weird string concatenation
// or incorrect math due to the data types in the inventory array.
totalValue += product.stock + product.valuePerUnit;
}
return totalValue;
}
console.log("Total Warehouse Value: " + calculateWarehouseValue(inventory));
Your Task:
- Copy this code into a browser's developer console or a JS file linked to an HTML page.
- Open the Sources tab in your browser's DevTools.
- Set a breakpoint inside the
forloop. - Add
totalValueto your Watch Expressions list. - Step through the loop iteration by iteration. Observe exactly which item causes
totalValueto stop being a number and start becoming a string (or producing an incorrect sum). - Fix the logic error in the
calculateWarehouseValuefunction so that it correctly multiplies stock by value and handles the string "15" correctly.
There are no comments for now.