← All concepts
Fundamentals: Part 2

let, const, var, and the Temporal Dead Zone

let and const are block-scoped and unreachable until their line runs, while var is function-scoped and available as undefined from the top.

Definition

`var` declarations are function-scoped (or global-scoped) and hoisted with an initial value of `undefined`, so a `var` variable exists and reads as `undefined` from the very top of its scope, even before its declaration line runs. This makes `var` easy to misuse: it ignores block boundaries like `if` and `for`, so the same name can leak out of a loop or conditional. `let` and `const` are block-scoped, confined to the nearest `{}`. They are registered ahead of time like `var`, but they stay in the temporal dead zone (TDZ) from the start of the block until their declaration line actually executes. Accessing a `let`/`const` variable inside its TDZ throws a `ReferenceError` instead of silently returning `undefined`. `const` additionally forbids reassigning the binding after it is set (though the value it points to, if an object, can still be mutated).

Examples

console.log(count); // ReferenceError: Cannot access 'count' before initialization
let count = 5;
console.log(count); // 5

count exists as soon as the block starts, but it stays in the temporal dead zone until its declaration line runs, so reading it earlier throws instead of returning undefined.

console.log(msg); // undefined (no error)
var msg = "hi";
console.log(msg); // "hi"

var is hoisted and initialized to undefined immediately, so reading it before its assignment line is legal but returns undefined rather than the eventual value.

for (var i = 0; i < 3; i++) {}
console.log(i); // 3, i leaked out of the loop

for (let j = 0; j < 3; j++) {}
console.log(j); // ReferenceError, j is block-scoped to the loop

var ignores the for loop's block boundary and leaks into the enclosing scope, while let is confined to the loop body.

Common mistakes

  • Assuming a `var` declared inside an `if` or `for` block stays scoped to that block; it actually leaks into the whole function.
  • Reading a `let`/`const` before its declaration line and expecting `undefined`, when it actually throws a ReferenceError.
  • Thinking `const` makes an object fully immutable; it only locks the binding, so the object's own properties can still be changed.

Key takeaways

  • `var` is function-scoped and hoisted as `undefined`; `let`/`const` are block-scoped and sit in the TDZ until their line runs.
  • Reading a `let`/`const` before its declaration throws a ReferenceError, not `undefined`.
  • `const` locks the variable binding, not the value it points to.
Check your understanding

`var i` is declared inside a `for` loop. What happens to `i` after the loop finishes?

Where you see this

  • Choosing let/const by default so a typo or early read throws immediately instead of silently producing undefined.
  • Avoiding the classic var-in-a-loop bug where every closure captures the same leaked variable.
  • Reasoning about a 'Cannot access before initialization' error, which points at a TDZ violation, not a missing declaration.
  • Using const to signal a binding will never be reassigned, catching accidental reassignment at parse time.

Practice this

Further reading

Previous
Operator Precedence and Associativity
Next
Truthy and Falsy Values