← all labs  ·  ← prev lab  ·  next lab →

Foundation Lab 07

var, let, const & the Temporal Dead Zone

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 & SCOPE 2 of 10
▶
EXECUTION 10 of 10

Every language needs rules for where a name is visible. JavaScript has three different declaration forms with three different answers.

Mental Models

var registers open, let/const register locked Two boxes from the creation phase: one open and readable, one behind a padlock. undefined var 🔒 let / const

Mental Model 1 of 5Registered Open vs Registered Locked

The Temporal Dead Zone is a span of time, not a place in the code A timeline from the start of a block to a declaration line, marked as a dead zone, then open after. TDZ readable declaration line

Mental Model 2 of 5The TDZ Is a Span of Time

var escapes a block, let stays contained An outer box representing a function, with an inner box representing an if-block; var crosses the inner boundary, let stays inside it. if-block let x var y escapes to here

Mental Model 3 of 5Block Scope vs Function Scope

An analogy: a locked drawer with a countdown timer A drawer with a visible lock and a small countdown timer; once the timer reaches the declaration line, the lock pops open. 🔒 timer

Mental Model 4 of 5An Analogy: A Drawer With a Timer

Misconception: a TDZ error looks like an undeclared-name error but is not the same Two similar-looking error boxes with a magnifying glass showing they have different causes underneath. "before initialization" "is not defined" both are ReferenceErrors — different causes

Mental Model 5 of 5Misconception: Two Different ReferenceErrors

Learning Objectives

  1. 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.
  2. 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.
  3. Objective 3 of 4 — I can distinguish a TDZ ReferenceError from an undeclared-identifier ReferenceError (Lab 04) by what actually caused each one.
  4. 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.

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

let favoriteColor = "teal";
console.log("after declaration:", favoriteColor);

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.

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?

Reflection 2 of 3. Would you rather debug a silent undefined from var, or a loud TDZ error from let? Why?

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.


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).

← All labs  ·  Lab 08 — The Global Object & Global Execution Context →