Debugging in DevTools

Intermediate · 18 min read · ▶ live playground · ✦ checkpoint

Guessing is the slowest debugger. Debugging is finding the exact place where your program's behavior first stops matching your expectation, and DevTools gives you the tools to pause the engine and look.

Console tools beyond log

You already use console.log as a lab bench. For larger data, the Console has sharper instruments:

  • console.table shows an array of objects as rows and columns.
  • console.group and console.groupEnd fold related logs under one label.
  • console.time and console.timeEnd measure how long a named piece of code takes.

Think of these like switching from loose sticky notes to a labeled clipboard. Same facts, less mess.

const expenses = [
  { label: "Coffee", category: "food", cents: 450 },
  { label: "Notebook", category: "work", cents: 1200 },
  { label: "Lunch", category: "food", cents: 1200 },
];
 
console.table(expenses);
 
console.group("food expenses");
for (const expense of expenses.filter(item => item.category === "food")) {
  console.log(`${expense.label}: ${expense.cents} cents`);
}
console.groupEnd();
 
console.time("food total");
const foodTotal = expenses
  .filter(expense => expense.category === "food")
  .reduce((total, expense) => total + expense.cents, 0);
console.timeEnd("food total");
 
console.log(foodTotal);

This is a DevTools recipe, not a promise about exact text output. In the browser Console, table gives you a visual table, group gives you a collapsible section, and timeEnd prints a real duration whose number changes from run to run.

Use the right console tool for the question:

  • Use table when you are scanning many records.
  • Use group when several lines belong to one case.
  • Use time when you are checking whether a change made code faster or slower.

Try it — reproduce the bug first

Run this. The program is clean JavaScript, but the result is wrong. Before you edit anything, write down the expected cents by hand.

javascript — live consolereal engine
⌘/Ctrl + Enter to run

The output says $12.00, but coffee plus lunch is 1650 cents, or $16.50. That gap is your starting evidence. Debugging begins with reproducing the bug on purpose.

Breakpoints pause the engine

A breakpoint is a marker that tells DevTools, "pause here before running this line." It is like putting a finger on a sentence while reading a recipe: stop, check the ingredients, then continue.

For code in a file, open DevTools, go to the source/debugger panel, find the file, and click the line number inside totalForCategory. For this bug, the best line is the return matching.reduce(...) line, because that is where the suspicious total is built.

What you do:
1. Put a breakpoint on the reduce line.
2. Run the code again.
3. DevTools pauses before the reduce result is returned.
 
What you inspect:
matching
matching.length
category

At the pause, inspect matching. If it contains Coffee and Lunch, filtering is not the bug. That is one piece of the program cleared.

Step through the suspicious line

Step-through debugging means running one small piece of code at a time while the program is paused. Think of it as replaying a video frame by frame instead of watching the blur at full speed.

DevTools gives you buttons with names like Step over, Step into, and Resume. The exact icons vary by browser, but the moves are stable:

  • Resume keeps running until the next breakpoint.
  • Step over runs the current line and pauses on the next line in the same function.
  • Step into follows a function call so you can see what happens inside it.

For the reduce bug, step into the callback. You want to see the values as each food expense moves through the accumulator machine from lesson 6.3.

Watch expressions while paused inside the reduce callback:
total
expense.label
expense.cents
total + expense.cents
 
First callback pause:
total              0
expense.label      "Coffee"
expense.cents      450
total + expense.cents 450
 
Second callback pause:
total              450
expense.label      "Lunch"
expense.cents      1200
total + expense.cents 1650

A watch expression is an expression DevTools recalculates every time execution pauses. It is a dashboard gauge for the value you care about.

The watch list exposes the bug: the correct expression is total + expense.cents, but the code returns only expense.cents. The function keeps replacing the total with the current row.

Read the call stack

The call stack is the current stack of function calls that led to the paused line, newest call on top. Use the stack-of-plates picture: each function call puts a plate on top; returning removes it.

In DevTools, the call stack tells you who called the function you are inspecting. That matters when the same helper is used from many places.

Call stack while paused:
reduce callback
totalForCategory
summarizeBudget
global

Read it from top to bottom as "where am I, and who asked me to be here?" global means the top-level script outside any function. You do not need the full engine model yet; lesson 10.1 will go deeper. For debugging today, the stack answers "which path brought me to this bad value?"

Use debugger when the line matters

The debugger statement is a line of code that acts like a breakpoint when DevTools is open. Without DevTools attached, the engine just keeps going.

function totalForCategory(expenses, category) {
  const matching = expenses.filter(expense => expense.category === category);
 
  debugger;
 
  return matching.reduce((total, expense) => total + expense.cents, 0);
}

Use debugger for a short investigation, then remove it. Leaving it in shared code is like leaving caution tape across a hallway after the repair is done.

The debugging loop

Write your debugging notes in this shape:

Observation:
Food total prints $12.00, but Coffee + Lunch should be $16.50.
 
Hypothesis:
The filter is dropping one food expense.
 
Experiment:
Pause after matching is created and inspect matching.length and matching labels.
 
Conclusion:
matching has Coffee and Lunch, so filter is fine. The reduce callback is suspicious.

Then repeat with a narrower hypothesis. The point is not to sound formal. The point is to stop thrashing.

For the playground bug, the final fix is small:

function totalForCategory(expenses, category) {
  const matching = expenses.filter(expense => expense.category === category);
  return matching.reduce((total, expense) => total + expense.cents, 0);
}
 
console.log(totalForCategory([
  { label: "Coffee", category: "food", cents: 450 },
  { label: "Lunch", category: "food", cents: 1200 },
], "food"));

1650.

Project 2 is where this becomes real work: you will build a data cruncher, validate messy records, and debug the places where totals, filters, and JSON do not behave the way you expected. Your job will not be to guess harder; it will be to gather better evidence.

Checkpoint

Answer all three to mark this lesson complete

+50 XP on completion