Q34 of 40 · JavaScript

What is the temporal dead zone (TDZ), and how does it affect `let`, `const`, and `class`?

JavaScriptSeniorjavascripttemporal-dead-zonetdzletconsthoistingscope

Short answer

Short answer: The TDZ is the period between when a `let`/`const`/`class` binding is created (block entry) and when it is initialized (the declaration line). Accessing the variable in this window throws `ReferenceError`. This prevents accessing variables before their definition, unlike `var` which initializes to `undefined`.

Detail

The temporal dead zone is a specified behaviour in the ES2015+ spec, not an implementation detail.

Why it exists: var hoisting silently initializes to undefined, which is a common source of bugs. The TDZ for let/const was deliberately designed to make such access a visible error.

Scope vs initialization: All let/const/class bindings are hoisted to the top of their enclosing block — they are in scope immediately. But they are not initialized until the declaration is reached. The TDZ is the region in scope but not yet initialized.

class declarations: class is subject to the same TDZ as let. A class cannot be instantiated before its declaration. This differs from function declarations, which are fully hoisted.

Tricky scenario — closures: A function defined before a let declaration that references that let is fine, as long as the function is not called until after initialization. The reference is resolved at call time, not at definition time.

In test code: TDZ errors in tests typically arise from module-level constants referenced in describe block headers that run before all imports are evaluated, or from circular imports in Jest where a module references another before it's initialized.

// EXAMPLE

// TDZ demonstrated
{
  // TDZ for x begins here (x is in scope but not initialized)
  // console.log(x); // ReferenceError: Cannot access 'x' before initialization
  let x = 5;
  // TDZ for x ends here
  console.log(x); // 5 — fine
}

// var — no TDZ
{
  console.log(y); // undefined (no error)
  var y = 5;
}

// class — TDZ applies
// const c = new MyClass(); // ReferenceError
class MyClass {}
const c = new MyClass(); // fine

// Closure + TDZ — not a problem if called after init
function useZ() { return z; } // reference is resolved at call time
let z = 10;
useZ(); // 10 — fine, z is initialized before the call

// WHAT INTERVIEWERS LOOK FOR

Understanding that hoisting and initialization are separate steps. The scope (in TDZ) vs initialization (leaves TDZ) distinction. `class` subject to TDZ like `let`. The closure-and-TDZ scenario (safe if called after init). A clear answer here signals deep JS engine knowledge.

// COMMON PITFALL

Saying 'let and const are not hoisted' — they are hoisted (in scope), just not initialized. The TDZ is the region where they are in scope but inaccessible.