Compile Ready
Module 4 · Execution Context & Hoisting

The Temporal Dead Zone

Intermediate10m read10m practice20m total
TDZletconstclassScope

Introduction

The Temporal Dead Zone (TDZ) is the period between entering a scope and initializing a let, const, or class binding. The name exists, but JavaScript refuses to let you read or write it yet.

This is why let and const feel stricter than var: early access fails loudly instead of returning undefined.

Why This Matters

TDZ questions separate memorization from real understanding. Senior interviewers often use shadowing, typeof, class declarations, and default parameters to see whether you know that lexical bindings are created early but initialized later.

Theory

The precise rule

For let, const, and class, the binding is created when the scope is entered. It is initialized only when execution reaches the declaration. Any access before initialization throws ReferenceError.

TDZ is about time, not just position

The word temporal matters. A line of code may appear after a declaration but still run before it due to a function call, or it may appear before a declaration but never run. TDZ errors happen at runtime when an access occurs before initialization.

Shadowing can create surprising TDZ errors

An inner let shadows an outer variable for the entire block, including lines before the inner declaration. So a read at the top of the block does not fall back to the outer variable; it hits the inner uninitialized binding and throws.

typeof exception has an exception

typeof notDeclared returns 'undefined' for a truly undeclared name. But typeof tdzName throws ReferenceError if tdzName is a lexical binding in the current scope that has not initialized yet.

Default parameter TDZ

Parameters are initialized left to right. A default value can read earlier parameters, but it cannot read later parameters that are not initialized yet. function f(a = b, b = 1) {} throws when called with no arguments.

Why TDZ exists

TDZ prevents subtle bugs. Instead of silently using undefined, JavaScript tells you that your code is relying on a value before it has been initialized. It also makes block scope and const semantics sound: a const binding should not exist in a usable, unassigned state.

Visual Diagrams

TDZ timeline for a block
enter block
  |
  | binding exists but is uninitialized
  | reads and writes throw ReferenceError
  v
let value = 10
  |
  | binding initialized
  v
value can be read and written according to declaration rules
  |
leave block

The binding exists before initialization, but it is unusable.

Shadowing and TDZ
outer value = outer

{
  inner value binding created at block entry
  console.log(value) hits inner TDZ, not outer
  let value = inner
}

The inner lexical binding shadows the outer name for the whole block.

Code Examples

Shadowing creates a TDZ trap

The inner let user shadows the outer user from the start of the block.

Loading…

typeof is not always safe

A truly undeclared name is safe with typeof, but a TDZ binding is not.

Loading…

Playground

Loading editor…
Console

Press Run to execute the code and see output here.

Output Prediction

Predict the output #1

javascript
1let value = 'outer';
2
3{
4 try {
5 console.log(value);
6 } catch (error) {
7 console.log(error.name);
8 }
9
10 let value = 'inner';
11 console.log(value);
12}
13
14console.log(value);

Predict the output #2

javascript
1console.log(typeof missing);
2
3try {
4 console.log(typeof token);
5} catch (error) {
6 console.log(error.name);
7}
8
9let token = 'abc';
10console.log(typeof token);

Predict the output #3

javascript
1function demo(a = b, b = 2) {
2 console.log(a);
3 console.log(b);
4}
5
6try {
7 demo();
8} catch (error) {
9 console.log(error.name);
10}
11
12function okay(a = 1, b = a + 1) {
13 console.log(a);
14 console.log(b);
15}
16
17okay();

Coding Exercises

Make configuration initialization TDZ-safe

Medium

Implement buildUrl(options) without reading any variable before it is declared. It should default protocol to 'https', default host to 'example.com', default path to '/home', and return protocol + '://' + host + path.

Interview Questions

1What is the temporal dead zone?

The TDZ is the period from entering a scope until a let, const, or class declaration is initialized. The binding exists during that period, but any access throws ReferenceError. This is why lexical declarations are hoisted but not usable before their declaration line executes.

Asked at:GoogleMicrosoftAmazon

Follow-ups

  • Does typeof throw in the TDZ?
  • Why does inner let shadow an outer variable before its declaration line?
2Why does `typeof x` sometimes throw?

typeof only safely returns undefined for names that are truly undeclared. If x is a lexical binding in the current scope but is still uninitialized in the TDZ, typeof x triggers the same ReferenceError as a normal read. The name exists; it is just not initialized yet.

Asked at:MetaApple

Quiz

1. When does the TDZ for a `let` binding end?

2. What does `typeof missingName` return when `missingName` is truly undeclared?

3. Which binding can be read before its declaration line and produce `undefined`?

Summary

  • TDZ runs from scope entry until a lexical or class declaration initializes.
  • `let`, `const`, and `class` are hoisted as bindings but cannot be accessed in the TDZ.
  • An inner lexical binding shadows outer bindings even before its declaration line.
  • `typeof` is safe for undeclared names but throws for TDZ bindings.

Cheat Sheet

TDZ applies to: let, const, class (and many module-binding scenarios).

Starts: when the scope is entered.

Ends: when execution initializes the declaration.

Early access: ReferenceError, including typeof for TDZ bindings.

Shadowing: inner lexical binding wins for the whole block, even before its declaration line.