let and const go through the same two-phase registration as
var — they just come out the other side in a very different, much stricter state.
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: Common Concept, JavaScript-Specific Rules
◎
NAMES & SCOPE2 of 10
▶
EXECUTION10 of 10
Every language needs rules for where a name is visible. JavaScript has three different
declaration forms with three different answers.
Mental Models
Mental Model 1 of 5Registered Open vs Registered Locked
Both get a slot in the creation phase. var's slot is immediately readable; let/const's slot is locked until its own declaration line runs.
Mental Model 2 of 5The TDZ Is a Span of Time
From the start of the block until its own declaration line executes, a let/const name exists but touching it throws.
Mental Model 3 of 5Block Scope vs Function Scope
let/const stay contained to the block they're declared in. var ignores block boundaries and belongs to the whole enclosing function (or the global scope).
Mental Model 4 of 5An Analogy: A Drawer With a Timer
Imagine a drawer that exists — you can see it's there — but it's locked with a countdown timer that only unlocks when execution reaches the drawer's own declaration line. Try to open it early and it doesn't just refuse quietly; it sets off an alarm (a thrown error) instead.
Mental Model 5 of 5Misconception: Two Different ReferenceErrors
Both a TDZ violation and Lab 04's undeclared-identifier case throw a ReferenceError, which makes them easy to conflate. They're not the same failure: the TDZ error means the name exists but isn't initialized yet; the undeclared-name error means the name was never registered anywhere at all.
Learning Objectives
Objective 1 of 4 — I can explain the Temporal Dead Zone as a span of time within a block, not a location in the source code.
Objective 2 of 4 — I can predict, for a given point in the code, whether a var-, let-, or const-declared variable is currently readable.
Objective 3 of 4 — I can distinguish a TDZ ReferenceError from an undeclared-identifier ReferenceError (Lab 04) by what actually caused each one.
Objective 4 of 4 — I can explain, in concrete terms, why modern style guides prefer const/let over var.
Pre-Lab Quiz
Answer from intuition first.
Question 1 of 5You read a let-declared variable on the line before its own declaration, in the same block. What happens?Open question
Question 2 of 5A var is declared inside an if-block. Can it be read from outside that if-block, but still inside the same function?Open question
Question 3 of 5Same question, but with a let declared inside that if-block instead. Readable from outside it?Open question
Question 4 of 5const total = 100; is followed later by total = 200;. What happens?Open question
Question 5 of 5Two separate, nested if-blocks each declare their own let count = 0;. Do they refer to the same variable or two independent ones?Open question
The Concept
Block-scoped variables aren't a JavaScript invention — the TDZ is
Most languages have some notion of a variable being scoped to a smaller block than a whole
function. What's specific to JavaScript is the Temporal Dead Zone: a
deliberately strict, loud-failure zone that let/const sit in
between being registered and being declared.
Cross-language note
Common concept: variables scoped to a block.
Python: has no block scope at all — a variable assigned inside an
if or for is visible in the whole enclosing function afterward,
closer to how var behaves than how let does.
JavaScript:let/const are genuinely trapped
inside the block (third diagram above) — a behavior Python simply doesn't have an
equivalent for.
Same creation phase, different starting state
Building on Lab 06: let and const are registered during the
creation phase too — the name exists from the very start of its block. But instead of
starting as undefined like var, it starts uninitialized,
in the Temporal Dead Zone. Touching it before its own declaration line runs throws
ReferenceError: Cannot access '...' before initialization — a deliberately
loud failure instead of a silent undefined.
Scope: function-wide vs block-contained
The second difference is scope shape. A block is simply any region of
code wrapped in { } — the body of an if, a for loop,
or just a bare pair of braces on their own. var belongs to the nearest
enclosing function (or the global scope if there is none) — it ignores block
boundaries entirely. let and const are genuinely
block-scoped: they exist only inside the specific { } they
were declared in, and stop existing entirely once that block ends.
const adds one more rule on top: the binding itself is locked
Everything above about the TDZ and block scope applies equally to let and
const — they share the same creation-phase behavior. const adds
one additional restriction: once its declaration line runs and assigns a value, that
specific name can never be reassigned to point at a different value again. Attempting to
do so throws TypeError: Assignment to constant variable, immediately, not
silently. (This locks the binding — the name-to-value connection — not necessarily
the contents of an object the binding points at; a later lab on objects covers that
distinction directly.)
Lab
Primary objective: trigger the TDZ directly, and see block scope contain a let while a var escapes.
You'll need: a browser console or node — no project, no files.
Lab 1 of 1 — Trigger the TDZ and test block scope
Task
Read a let early (inside a try/catch), then test whether a var and a let each escape an if-block.
Instructions
Step 1 of 4
// Paste this into a scratch file or console. Read it — do not run it yet.
This step is complete when: you've predicted what the try block logs, before running it.
Step 2 of 4
Predict what the catch block logs, then reveal.
threw: Cannot access 'favoriteColor' before initialization
after declaration: teal
What you wrote: a read of favoriteColor before its own declaration
line, deliberately wrapped in try/catch to observe the failure
without stopping the script.
What the engine effectively does: favoriteColor was registered
during the creation phase, but starts in the Temporal Dead Zone --
uninitialized. Reading it there is treated as an error. Once
execution reaches "let favoriteColor = ...", the TDZ ends for that
name and it becomes a normal, readable variable.
This step is complete when: you've run the snippet yourself and it matches the revealed output.
Step 3 of 4
// Start a fresh snippet. Type it from these comments, don't paste finished code.
// Write: if (true) { ... } as a block.
// Inside the block, declare "var partySize = 4;"
// and also declare "let guestListLength = 4;".
// After the closing brace of the if-block, add two
// console.log calls: one for partySize, one for
// guestListLength (wrap the second in its own try/catch).
This step is complete when: your file has an if block declaring both variables, with both read attempts placed after the block closes.
Step 4 of 4
Predict which of the two reads (outside the block) succeeds and which throws. Run it and compare, then answer this comprehension check: is the error you get for guestListLength here the same kind of error as reading it early inside the TDZ, or a different one — and why?
Comprehension Checklist
I can explain the TDZ as a span of time within a block, not a location in the source code.
I know var ignores block boundaries; let/const are genuinely trapped inside them.
I can tell apart a TDZ ReferenceError (name exists, not yet initialized) from an undeclared-name ReferenceError (name never exists there at all, from Lab 04).
I know const's reassignment lock is a separate rule from the TDZ/block-scope behavior it shares with let.
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 5What state does a let variable start in during the creation phase?Open question
Question 2 of 5A let is declared inside an if-block. Can code after the block's closing brace read it successfully?Open question
Question 3 of 5What's the practical difference between var's "silent undefined" and let/const's TDZ error, when something's read too early?Open question
Question 4 of 5const guestListLength = 4; is followed by guestListLength = 5;. What happens, and is this the TDZ or a different rule?Open question
Question 5 of 5A colleague says "let and const solve exactly the same problem as var, just with new keywords." Based on this lab, what's wrong with that claim?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. Given what you now know, why might modern style guides recommend const by default, let only when reassignment is needed, and var essentially never?
const/let give you block scope (fewer accidental leaks past an
if or for) and a loud TDZ error instead of a silent
undefined when something's used too early. var's looser rules
made sense before block scope existed, but now mostly just remove safety nets without
adding any capability let/const lack.
Reflection 2 of 3. Would you rather debug a silent undefined from var, or a loud TDZ error from let? Why?
Most developers prefer the loud error — it points directly at the exact line and reason,
versus an undefined that might not cause a visible problem until much later
in the program, far from where the actual mistake was made.
Reflection 3 of 3. Looking back across Labs 03 through 07 as a group: how did today's lab build on the two-phase creation/execution model from Lab 06, and how did that in turn depend on the execution-context model from Lab 05? Trace the chain in your own words.
Lab 05 established that every function call gets its own execution context. Lab 06 opened
that context up and showed it's built in two passes — a creation phase, then an execution
phase. This lab took that exact two-phase model and asked "what if a name registers in a
locked state during the creation phase instead of an open one" — which is
precisely what let/const do. Each lab in this five-lab group
answered a question that only made sense because of the one before it.
What Breaks If This Understanding Is Wrong?
The scope chain (Lab 09) and closures (Lab 14) both depend on knowing exactly which block a
variable belongs to — mixing up var's function-wide scope with
let/const's block scope will make both of those labs' predictions
consistently wrong in loops and conditionals.
What JavaScript Concept Was Learned Today?
let and const go through the same creation-phase registration as
var, but land in a locked, block-scoped state (the Temporal Dead Zone) instead
of an open, function-scoped undefined — plus const's separate
reassignment lock on top.
Relates to: ◎ Names & Scope (2 of 10) — three declaration forms,
three different answers to "where is this name visible, and when does it become usable."
Next
That completes the first five-lab arc — from a single execution context on the call stack,
through how it's built in two phases, to the specific rules three different declaration
keywords follow inside that model. Lab 08 picks up a related but new thread: the global
object these top-level declarations actually attach to (or don't).