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 toundefined. 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 aReferenceError.- Function expressions and arrow functions assigned to
let/const/varfollow their binding’s hoisting rules, not the function-declaration rules — only the binding (not the function value) is hoisted.
Common mistakes
- Assuming
let/constaren’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 avardeclared inside anifblock to be scoped to that block.
Runtime notes
Hoisting behavior is defined by ECMAScript and identical across every current browser and Node version.