Hoisting

How each declaration type — var, function, let, const, class — is set up before line-by-line execution begins, and what's accessible before its own line runs.

Purpose

Before running a scope’s code line by line, JavaScript first sets up all of that scope’s declarations. “Hoisting” describes the visible effect: some names are usable before the line that declares them, though what’s actually available differs by declaration type.

Behavior by declaration type

console.log(varValue); // undefined — hoisted, default-initialized
console.log(fnDeclaration()); // works — fully hoisted, body included
console.log(letValue); // ReferenceError — temporal dead zone
var varValue = 1;
function fnDeclaration() {
  return 'hi';
}
let letValue = 2;
  • var: hoisted to the top of its enclosing function (not block), initialized to undefined. The assignment itself still only happens when its line runs.
  • Function declarations: hoisted with the entire function body attached — fully callable before their own line, anywhere in the enclosing scope.
  • let / const / class: hoisted in the sense that the engine already knows the binding exists, but it stays uninitialized — in the temporal dead zone — until its declaration line actually executes. Accessing it before that throws a ReferenceError.
  • Function expressions and arrow functions assigned to let/const/var follow their binding’s hoisting rules, not the function-declaration rules — only the binding (not the function value) is hoisted.

Common mistakes

  • Assuming let/const aren’t hoisted at all, rather than hoisted-but-uninitialized — the temporal dead zone specifically depends on the binding existing early, just not being usable yet.
  • Relying on var’s function-scoped hoisting for control flow, such as expecting a var declared inside an if block to be scoped to that block.

Runtime notes

Hoisting behavior is defined by ECMAScript and identical across every current browser and Node version.

Taught in these lessons

Related reference