0 / 91
Week 4 · Day 24 of 91

Errors and debugging

JavaScript Foundations II and the Browser

Objective

Read failures, reproduce them, and isolate the smallest broken assumption.

Events are interrupts, promises represent future results, and the DOM is the browser’s live model of the page.

  • syntax, runtime, and logic errors
  • stack traces
  • breakpoints and watch values

Why this matters

Yesterday your project became four files. The moment code spans files, "it doesn't work" stops being a sentence you can act on — you need to know which line, in which file, holding which value. Today you learn to get that answer from evidence instead of by rereading the code hopefully.

By the end of the hour you can name the three kinds of failure, read a stack trace top to bottom, and pause a running program to inspect its variables. This is the skill that decides whether a bug costs you five minutes or an evening.

Three kinds of error

They fail at different moments and they need different tools, so naming which one you have is the first move.

Syntax errors — the code is not valid JavaScript

The engine cannot even parse the file, so nothing runs at all, including the lines above the mistake. Missing brackets, quotes, or commas.

const total = 5
console.log("total is " total);
console.log("total is " total);
            ^^^^^^^^^^^

SyntaxError: missing ) after argument list

Note the caret line: the engine marks where it gave up. The real mistake is often a character or two earlier — here, a missing + — because the parser only notices once the text stops making sense.

Runtime errors — valid code that fails while executing

The file parses and starts running, then hits something impossible and stops at that point. Everything before it already happened.

const items = [{ name: "Resistor 10k" }];
function label(list, index) {
  return list[index].name.toUpperCase();
}
console.log(label(items, 3));
TypeError: Cannot read properties of undefined (reading 'name')
    at label (/Users/you/fullstack-journey/inventory-app/bug.js:3:22)
    at Object.<anonymous> (/Users/you/fullstack-journey/inventory-app/bug.js:5:13)

Read the message literally: something was undefined, and the code tried to read name off it. list[3] on a one-item array is undefined. TypeError and ReferenceError (a name that does not exist) are the two you will meet most.

Logic errors — it runs, it does not crash, the answer is wrong

The most dangerous kind, because nothing tells you. No message, no stack trace, no red text — just a number that is quietly incorrect.

const components = [
  { id: "R1", name: "Resistor 10k",    quantity: 42 },
  { id: "C1", name: "Capacitor 100nF", quantity: 10 },
  { id: "U1", name: "NE555 timer",     quantity: 3 },
];

function lowStock(items, threshold) {
  return items.filter((item) => item.quantity < threshold);
}

console.log(lowStock(components, 10).map((item) => item.id));
[ 'U1' ]

If your rule is "flag anything at or below the reorder level", that answer is wrong: C1 sits exactly at 10 and was skipped, because < excludes the boundary and <= includes it. The program is delighted. Your stockroom is not. Only a check against an expected result finds this — which is why the discipline is to decide the answer before running the code.

Three fault classes on the bench

A syntax error is a board that will not power on — the fault is upstream of any measurement, and no probe helps until it is fixed. A runtime error is a protection circuit tripping mid-operation: the system ran, hit a condition it cannot handle, and shut down at a point you can locate. A logic error is a circuit that powers up, reads steady, and outputs 4.8 V where the spec says 5.0. Nothing is complaining. Only comparing against the expected value reveals it.

Reading a stack trace

A stack trace is the list of function calls that were in progress when the error happened, innermost first. It is a map of how execution arrived at the failure.

TypeError: Cannot read properties of undefined (reading 'name')
    at label (/Users/you/fullstack-journey/inventory-app/bug.js:3:22)
    at Object.<anonymous> (/Users/you/fullstack-journey/inventory-app/bug.js:5:13)
  • Line one is the error type and message. Read every word; it usually names the value and the operation.
  • The first at line is where it threw: file bug.js, line 3, column 22.
  • Each at line below is the caller of the one above. Line 5 called label.

So: line 3 broke, and the bad argument was supplied on line 5. Fixing line 3 alone would be treating the symptom — the caller passed an index that does not exist.

Skip the frames that are not yours

Node traces continue into node:internal/modules/cjs/loader and similar. Those are the runtime starting your program, never the bug. Find the topmost line that names your file and start there.

Following the call chain backwards

A parcel arrives damaged. The trace is the chain of custody: last handler at the top, sender at the bottom. The damage shows up at the last handler, but reading downward tells you who put a brick in the box.

Breakpoints and watching values

console.log is a fine first instrument, and it has a limit: you see only the values you thought to print, one snapshot at a time. A breakpoint pauses the program at a chosen line and lets you inspect everything in scope at that moment.

For browser code, use the Sources panel you met on Day 6. Open your served page, find your file in the file tree, click a line number to set the breakpoint, and reload. Execution stops there and the page freezes. Then:

  • The Scope pane lists every variable visible at that line and its current value. This is where yesterday's scope rules become visible: local names, then the enclosing scopes.
  • The Watch pane holds expressions you type — item.quantity, items.length — re-evaluated each time execution pauses.
  • Step over (F10) runs the next line and pauses again. Step into (F11) enters the function being called. Resume (F8) continues until the next breakpoint.

You can also set a breakpoint from code, with the debugger statement:

function lowStock(items, threshold) {
  debugger;
  return items.filter((item) => item.quantity < threshold);
}

When DevTools is open, execution pauses on that line. For a Node script, start it with the inspector attached:

node --inspect-brk reports.js
Debugger listening on ws://127.0.0.1:9229/8437efcb-0deb-4069-bb9b-72b6a0115e91

Then open chrome://inspect in Chrome and click inspect under the target. You get the same Sources panel, attached to Node. --inspect-brk breaks before the first line, so nothing runs before you are attached.

A breakpoint is a logic analyser trigger

A console.log is a scope probe on one net: you see that signal, and only where you clipped it. A breakpoint is a trigger condition on an analyser — when the condition hits, every channel is captured at once and the state is held for you to read. That is what the Scope pane is: all channels, at one instant.

Walkthrough

Reproduce the runtime error deliberately, then locate it with evidence.

cd ~/fullstack-journey/inventory-app

Create bug.js with the label example above and run it:

node bug.js

Read the output in this order, out loud: type (TypeError), message (something was undefined, .name was read from it), location (bug.js:3:22), caller (bug.js:5). Now test the assumption rather than guessing — add one line above the failure:

function label(list, index) {
  console.log("index:", index, "length:", list.length, "item:", list[index]);
  return list[index].name.toUpperCase();
}
index: 3 length: 1 item: undefined

The assumption "index 3 exists" is now disproved with evidence, not suspicion. The fix belongs at the caller (pass a valid index) or as a guard clause from Day 17:

function label(list, index) {
  const item = list[index];
  if (!item) return "unknown component";
  return item.name.toUpperCase();
}

Checkpoint

Say which of the three error kinds each of these is: a missing closing brace; reading .length of undefined; a total that is off by one item. Then say which tool you would reach for first for each.

Your turn

Build the deliverable: a debugging journal with three evidence-based fixes. Create ~/fullstack-journey/inventory-app/debug-journal.md.

  1. Syntax error. In a copy of reports.js, delete one closing parenthesis. Run it. Record the exact error text and the line the caret points at. Note whether any output appeared first.
  2. Runtime error. Change a filter callback to read a property that does not exist, such as item.stock.count. Run it and record the full error line plus the first two stack frames.
  3. Logic error. Change < to > in your low-stock filter. It will not crash. Record the output you got, the output you expected, and how you knew — a hand-checked expected list.
  4. For each of the three, write four lines in the journal: Symptom (what you observed), Evidence (error text, stack frame, or printed value), Cause (the smallest broken assumption), Fix (what you changed and why it works).
  5. Set one real breakpoint. Put debugger; inside a function in your served page, open DevTools, reload, and note two variable names and their values from the Scope pane in your journal.
  6. Remove every debugger; statement and temporary console.log before you finish.

You are done when

debug-journal.md has three entries, each naming an error kind, and each Evidence line quotes something the machine printed rather than something you assumed.

Reviewer mode — only after you have written the Evidence line yourself

Today's mode is reviewer. A good review returns specific, actionable findings backed by evidence — not praise, and not a rewrite of your file.

"Act as a debugger. Ask for error text, reproduction steps, and expected behavior before suggesting a fix."

If it proposes a fix before you have supplied all three, that fix is a guess. Make it ask.

Common pitfalls

  • Changing code before reproducing the failure. If you cannot make it fail on demand, you cannot prove you fixed it. Get a reliable reproduction first, every time.
  • Reading only the first word of the error. TypeError alone tells you almost nothing; the rest of the message names the value and the operation.
  • Starting at the bottom of the stack trace. The topmost frame in your own file is where it broke. The frames below say who called it.
  • Fixing the symptom. Adding if (!item) return; at the crash site silences it while the caller keeps passing bad data. Ask why the value was wrong before you defend against it.

Verify it yourself

Open today's reference, the Node.js Debugging guide.

  1. Find the section on the inspector. Does it agree that --inspect-brk breaks before user code runs, and what does plain --inspect do differently? Write both down.
  2. The guide mentions a security consideration about the inspector port. Find it and write one sentence on why you would not enable the inspector on a production server.

Add both answers to the bottom of debug-journal.md. Knowing what a debugging tool exposes is part of knowing when it is safe to use.

The hour

  1. 0–5 min Recall

    Without notes, state yesterday’s main idea and one unresolved question.

  2. 5–20 min Learn

    Read only the listed concept notes and official reference sections needed today.

  3. 20–48 min Build

    Introduce and then fix one error of each type. Record symptoms, cause, and fix.

  4. 48–55 min Explain and verify

    Run the result, inspect evidence, and explain the data/control flow in your own words.

  5. 55–60 min Quiz and commit

    Complete the quiz, record one lesson, and commit the verified change when applicable.

What to hand in

Deliverable

A debugging journal with three evidence-based fixes.

Working with AI today

AI as skeptical reviewer

Provide existing work and ask for concrete defects, risks, missing tests, and unsupported assumptions—not praise.

Act as a debugger. Ask for error text, reproduction steps, and expected behavior before suggesting a fix.

References

End-of-day quiz

Q1 Which error can produce wrong output without crashing?
Q2 Which result best proves today’s work is complete?
Q3 What should an AI code review primarily produce?

Explain-back gate

Pass the quiz above to unlock completion.

Quiz + explain-back checks required.