Without notes, state yesterday’s main idea and one unresolved question.
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
atline is where it threw: filebug.js, line 3, column 22. - Each
atline below is the caller of the one above. Line 5 calledlabel.
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.
- 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. - Runtime error. Change a
filtercallback to read a property that does not exist, such asitem.stock.count. Run it and record the full error line plus the first two stack frames. - 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. - 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).
- 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. - Remove every
debugger;statement and temporaryconsole.logbefore 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.
TypeErroralone 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.
- Find the section on the inspector. Does it agree that
--inspect-brkbreaks before user code runs, and what does plain--inspectdo differently? Write both down. - 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
- 0–5 min Recall
- 5–20 min Learn
Read only the listed concept notes and official reference sections needed today.
- 20–48 min Build
Introduce and then fix one error of each type. Record symptoms, cause, and fix.
- 48–55 min Explain and verify
Run the result, inspect evidence, and explain the data/control flow in your own words.
- 55–60 min Quiz and commit
Complete the quiz, record one lesson, and commit the verified change when applicable.
What to hand in
A debugging journal with three evidence-based fixes.
Working with AI today
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
Explain-back gate
Pass the quiz above to unlock completion.
Quiz + explain-back checks required.