Q34 of 40 · JavaScript
What is the temporal dead zone (TDZ), and how does it affect `let`, `const`, and `class`?
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