← all labs  ·  ← prev lab  ·  next lab →

Foundation Lab 04

undefined, null & Undeclared Identifiers

Three different kinds of "nothing," and they behave nothing alike. Confusing them is one of the easiest ways to misdiagnose a bug.

What this lab produces: no files, no commit — a pure concept lab. You'll run one short snippet in a console or with node.
Concept type: Mostly JavaScript-Specific
◇
VALUES & TYPES 1 of 10

"Represent no value" is part of the same question Lab 03 opened — JavaScript's answer here turns out to be three different answers layered together.

Mental Models

Three different kinds of absence Three boxes: an empty box the engine filled in, an empty box you filled in on purpose, and no box at all. ? Ø ✕ undefined null no box

Mental Model 1 of 5Three Kinds of "Nothing"

Reading a name that was never declared A magnifying glass searches the scope chain and finds nothing, resulting in an error. ? throw

Mental Model 2 of 5Undeclared Reads Throw

undefined is a storable value A hand places the word undefined into a labeled box, like any other value. undefined

Mental Model 3 of 5undefined Is a Real Value

An analogy: three mailboxes Three mailboxes on a street: one standing empty, one with a note reading nothing here on purpose, and an empty gap where no mailbox was ever installed. empty box "on purpose" note inside ∅ no box here

Mental Model 4 of 5An Analogy: Three Mailboxes

Misconception: undefined does not mean something went wrong A red X over a warning triangle, a green check over a plain value box. ✕ "error!" ✓ just a value

Mental Model 5 of 5Misconception: undefined ≠ "Something Broke"

Learning Objectives

  1. Objective 1 of 4 — I can distinguish undefined, null, and an undeclared identifier by what causes each one and what happens when you try to read it.
  2. Objective 2 of 4 — I can explain why null is always something a program chooses deliberately, while undefined is usually the engine's own default.
  3. Objective 3 of 4 — I can predict, for a given snippet, whether reading a name will return a value or throw a ReferenceError.
  4. Objective 4 of 4 — I can explain why typeof is safe to use on an undeclared name even though directly reading that name is not.

Pre-Lab Quiz

Answer from intuition first.

Question 1 of 5You write let score; with no assignment, then console.log(score). What happens?Open question

Question 2 of 5You write console.log(neverDeclared), where neverDeclared was never written anywhere else in the file. What happens?Open question

Question 3 of 5Is null just another spelling of undefined, or something else?Open question

Question 4 of 5Is declaring let x; and never assigning it considered an error by JavaScript?Open question

Question 5 of 5function greet(name) {...} is called as greet() with no argument at all. What does name hold inside the function?Open question


The Concept

"Absence of a value" is common; JavaScript's three-way split isn't

Most languages have some way to represent "no value" — but JavaScript is unusual in having three genuinely different situations that all sound like "nothing" in casual conversation. Telling them apart precisely is the actual skill this lab teaches.

Cross-language note

Common concept: representing "no value."

Python: has one deliberate "nothing" value, None, and reading a name that was never assigned anywhere raises NameError — conceptually close to JavaScript's undeclared-identifier case below.

JavaScript: splits this into two separate concepts — the engine's own default empty state (undefined) and your own deliberate empty state (null) — on top of the undeclared-identifier case Python already has an equivalent for.

The three cases, unpacked

A variable is declared when you introduce its name to the program with let, const, or var — that's the step that tells the engine "this name exists from now on," separate from actually giving it a value. Keeping that distinction in mind:

  1. undefined — a variable exists (it was declared) but was never assigned a value. This is the engine's own default, not something you typically write yourself. Think of it as the language's way of saying "this slot exists, and I'm showing you it's currently empty."
  2. null — a variable exists, and you deliberately assigned it the value null to mean "no value belongs here right now." It's a real primitive type (Lab 03), and you always assign it on purpose — the engine never produces it as a default the way it does with undefined.
  3. Undeclared identifier — the name was never declared anywhere. When JavaScript needs to look up a name, it performs what's called a scope-chain search: a walk outward through the nested places a variable could have been declared (Lab 09 covers this search in full). If that walk finishes without ever finding the name, the engine doesn't have an "empty" value to hand back — there's no slot at all — so it throws a ReferenceError instead.

Why does the distinction between undefined and null matter in practice?

If every "nothing" looked the same, you'd lose information a bug fix often depends on: was this value simply never set (a variable that's undefined, possibly a sign code forgot to assign it), or was it deliberately cleared out by some other part of the program (a variable that's null, a normal and expected state)? A crash report that says "cannot read properties of null" is telling you something different from one that says "cannot read properties of undefined" — the first usually means something was intentionally emptied and the code downstream wasn't ready for that; the second usually means something was never assigned in the first place.


Lab

Primary objective: observe all three cases side by side, including the one that throws.

You'll need: a browser console or node — no project, no files.

Lab 1 of 1 — Compare all three kinds of "nothing"

Task

Run all three cases, one of which is wrapped in try/catch so it doesn't stop the script.

Instructions

Step 1 of 4

// Paste this into a scratch file or console. Read it — do not run it yet.

let ticketCode;
console.log("declared, not assigned:", ticketCode);

let seatAssignment = null;
console.log("deliberately empty:", seatAssignment);

try {
  console.log("never declared:", boardingGroup);
} catch (err) {
  console.log("threw:", err.message);
}

This step is complete when: you've predicted all three outputs without running it.

Step 2 of 4

Predict all three outputs, then reveal.

This step is complete when: you've run the snippet yourself and it matches the revealed output.

Step 3 of 4

// Edit the file yourself. Don't paste finished code — type it from these comments.

// Below the existing code, add one more console.log:
// log the result of typeof boardingGroup directly,
// NOT inside a try/catch this time.

This step is complete when: the new line is added, outside of any try/catch.

Step 4 of 4

Predict what typeof boardingGroup does — does it throw the same way direct access did, or something else? Run it and compare, then answer: why might a language design typeof to behave this way specifically for undeclared names?


Comprehension Checklist

  • I can state, without hesitating, which of the three cases throws and which two don't.
  • I know null is always something a program assigns on purpose — the engine never hands it out as a default.
  • I know typeof on an undeclared name is safe (returns "undefined"), while directly reading it is not.
  • Given a bug report mentioning "null" versus one mentioning "undefined," I know these point to different root causes.

Post-Lab Quiz

These are different questions from the pre-lab quiz — they test what the experiment should have taught you, not the same questions again.

Question 1 of 5Which of the three cases in this lab is the only one that ever throws when read directly?Open question

Question 2 of 5Does the engine ever assign null to a variable on its own, without your code doing it explicitly?Open question

Question 3 of 5A bug report says "Cannot read properties of null." Based on this lab, what does that most likely mean?Open question

Question 4 of 5What does typeof someUndeclaredName return?Open question

Question 5 of 5function log(message) {...} is called as log() with nothing passed in. What does message hold, and which of today's three cases does that match?Open question


Reflection Questions

Write your own answer first — the blurred text is one possible answer, not the only correct one.

Reflection 1 of 3. When would you deliberately assign null to something instead of just leaving it undefined?

Reflection 2 of 3. Why might a language deliberately make typeof safe on undeclared names, when directly reading them throws?

Reflection 3 of 3. Before running today's experiment, what did you predict the "never declared" line would do? Did the actual behavior match, and if not, what was different about your mental model beforehand?


What Breaks If This Understanding Is Wrong?

Confusing "declared but empty" with "never declared" will make error messages feel random — you'll misread a real bug (a typo'd variable name causing a ReferenceError) as if it were just an empty value to work around, instead of fixing the actual typo.

What JavaScript Concept Was Learned Today?

The difference between the engine's own default empty value (undefined), your own deliberate empty value (null), and a name that doesn't exist at all (undeclared, throws).

Relates to: ◇ Values & Types (1 of 10) — three different answers to "how does the language represent nothing," layered inside one type system.


Next

Lab 05 shifts to a completely different question: not "what values exist," but "how does JavaScript actually run a piece of code, step by step." That's a different axis entirely — the execution context and call stack — and it's the foundation everything from Lab 06 onward builds on.

← All labs  ·  Lab 05 — Execution Context & the Call Stack →