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 & TYPES1 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
Mental Model 1 of 5Three Kinds of "Nothing"
A box that exists but was never filled, a box you deliberately emptied, and a spot with no box at all — three completely different situations.
Mental Model 2 of 5Undeclared Reads Throw
If the scope chain search from Lab 09 never finds the name at all, the engine doesn't hand back a placeholder — it throws.
Mental Model 3 of 5undefined Is a Real Value
You can assign undefined to a variable yourself — it's a value with its own type (Lab 03), not just an error state.
Mental Model 4 of 5An Analogy: Three Mailboxes
Imagine three addresses on a street. The first has a mailbox that's just empty — nobody's put mail in it yet (undefined). The second has a mailbox with a note deliberately left inside saying "nothing here on purpose" (null). The third address never had a mailbox installed at all — there's nowhere to even look (undeclared).
Mental Model 5 of 5Misconception: undefined ≠ "Something Broke"
Seeing undefined in a console can feel alarming if you're used to languages where "nothing" means a crash. In JavaScript, undefined is a completely ordinary, valid value with its own type (Lab 03) — logging it is not, by itself, evidence that anything went wrong.
Learning Objectives
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.
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.
Objective 3 of 4 — I can predict, for a given snippet, whether reading a name will return a value or throw a ReferenceError.
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:
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."
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.
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.
declared, not assigned: undefined
deliberately empty: null
threw: boardingGroup is not defined
What you wrote: three variables in three different states, one
inside a try/catch specifically because it's expected to throw.
What the engine effectively does: ticketCode exists in the scope
chain with the engine's default empty value. seatAssignment exists
with the value you chose yourself. boardingGroup was never
registered anywhere the scope-chain search can find, so reading it
is a hard failure, not an empty result.
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?
When "no value yet" is a meaningful state your own code chose, versus a variable that
simply hasn't been touched. For example, a selectedSeat field that starts as
null communicates "the user hasn't picked one," which reads differently from
a variable that was just never assigned by accident.
Reflection 2 of 3. Why might a language deliberately make typeof safe on undeclared names, when directly reading them throws?
It gives code a way to safely ask "does this even exist" without wrapping every check in
a try/catch — useful for feature-detection style code checking whether some
optional global exists before using it.
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?
A common prediction is that it would print undefined, the same as the
declared-but-unassigned case — since both "feel like nothing" from the outside. The
actual behavior (a thrown error) reveals that JavaScript treats "no such name exists" as
a fundamentally different, more severe situation than "this name exists but is empty."
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.