What Is Hoisting
Hoisting is JavaScript’s behavior of moving declarations to the top of their scope before the code executes. It’s not a physical movement of your code — it’s what happens during the compilation phase, before a single line of your program runs.
JavaScript has two phases when it processes your code:
┌──────────────────────────────────────────────────────────────────────┐│ JAVASCRIPT'S TWO PHASES ││ ││ Phase 1: COMPILATION ││ ───────────────────────────────────────────────────────── ││ JavaScript scans your entire code. ││ It finds all var declarations, function declarations, ││ and registers them in memory — before running anything. ││ ││ Phase 2: EXECUTION ││ ───────────────────────────────────────────────────────── ││ JavaScript runs your code line by line. ││ By now, declarations are already known. ││ Assignments still happen in order, top to bottom. │└──────────────────────────────────────────────────────────────────────┘This two-phase process is why you can call a function before it appears in your file, or why accessing a var variable before its line doesn’t throw an error (though it gives you undefined).
How var Is Hoisted
Variables declared with var are hoisted to the top of their function scope (or global scope if declared at the top level). The declaration is hoisted, but the assignment is not.
// What you write:console.log(name); // What happens here?var name = 'Alice';console.log(name);
// What JavaScript actually sees (mental model):var name; // declaration is hoisted, initialized to undefinedconsole.log(name); // undefined ← NOT an error, NOT 'Alice'name = 'Alice'; // assignment stays in placeconsole.log(name); // 'Alice'This is one of the most common sources of bugs in JavaScript. The variable exists but has no value yet.
// A more dangerous example — loop variable leakingfunction processItems() { for (var i = 0; i < 5; i++) { // do something } console.log(i); // 5 — i is still accessible! var is function-scoped}
// var leaks out of if blocks tooif (true) { var secretMessage = 'hello';}console.log(secretMessage); // 'hello' — NOT a ReferenceError!
// Compare with let (block-scoped):if (true) { let blockMessage = 'hello';}console.log(blockMessage); // ReferenceError: blockMessage is not defined ✅How Function Declarations Are Hoisted
Function declarations are hoisted completely — both the declaration AND the body. This means you can call a function before it appears in your code, and it works perfectly.
// Calling a function before it's defined — works fine!greet('Alice'); // 'Hello, Alice!'
function greet(name) { console.log(`Hello, ${name}!`);}
// JavaScript sees this during compilation:// → function greet is stored in memory with its full body// → then the greet('Alice') call runs and finds the functionThis is actually a feature, not a bug. It lets you organize your code so the important high-level functions appear at the top and the implementation details are lower down — even though the helpers are called by the top-level code.
// You can organize code for readability — high level firstfunction runApplication() { const users = loadUsers(); // defined later in the file const filtered = filterActive(users); // defined later in the file display(filtered); // defined later in the file}
runApplication(); // This works perfectly
// ─────────── Helper functions below ───────────
function loadUsers() { return [ { name: 'Alice', active: true }, { name: 'Bob', active: false }, { name: 'Carol', active: true }, ];}
function filterActive(users) { return users.filter((user) => user.active);}
function display(items) { items.forEach((item) => console.log(item.name));}Function Expressions Are NOT Fully Hoisted
This is where many developers get caught out. There’s a crucial difference between function declarations and function expressions.
// ── Function Declaration ──────────────────────────────────────────────// Declaration: hoisted completely. Can be called before the line.console.log(add(2, 3)); // 5 ✅
function add(a, b) { return a + b;}
// ── Function Expression ───────────────────────────────────────────────// Expression assigned to var: the VAR is hoisted (as undefined), not the functionconsole.log(multiply(2, 3)); // TypeError: multiply is not a function ❌// Why? multiply is hoisted as 'undefined', and undefined is not callable
var multiply = function (a, b) { return a * b;};
// ── Arrow Function Expression ──────────────────────────────────────────// Same behavior as function expressionconsole.log(divide(10, 2)); // ReferenceError: Cannot access 'divide' before init ❌
const divide = (a, b) => a / b;The different error messages are telling:
TypeError: multiply is not a function—var multiplywas hoisted asundefined, and you tried to callundefined().ReferenceError: Cannot access 'divide' before initialization—const dividewas hoisted but put in the Temporal Dead Zone (explained next).
let and const — Hoisted but Not Initialized (The Temporal Dead Zone)
let and const ARE hoisted — they are registered during the compilation phase just like var. The crucial difference: they are placed in the Temporal Dead Zone (TDZ) until their declaration line is reached.
┌──────────────────────────────────────────────────────────────────────┐│ THE TEMPORAL DEAD ZONE (TDZ) ││ ││ Start of scope ││ ─────────────── ││ │ ││ │ ← TDZ for 'username' begins here ││ │ (variable is registered but accessing it throws ReferenceError)││ │ ││ │ console.log(username); // ❌ ReferenceError ││ │ ││ │ let username = 'Alice'; // ← TDZ ENDS here. Variable is init'd ││ │ ││ │ console.log(username); // ✅ 'Alice' ││ │ ││ End of scope │└──────────────────────────────────────────────────────────────────────┘// TDZ in actionfunction example() { // TDZ for 'city' starts here console.log(typeof city); // ReferenceError (NOT 'undefined' like with var)
let city = 'London'; // TDZ ends here console.log(city); // 'London'}
// Contrast with var — no TDZfunction exampleVar() { console.log(typeof country); // 'undefined' (no error — var has no TDZ) var country = 'UK'; console.log(country); // 'UK'}
// The TDZ applies even in the same blocklet value = 10;
function tricky() { // 'value' is hoisted to the TOP of this function scope (TDZ begins) // The outer 'value = 10' is NOT accessible here console.log(value); // ReferenceError — in TDZ! let value = 20; // TDZ ends here console.log(value); // 20}
tricky();Hoisting With Classes
Class declarations are hoisted like let and const — they exist in the TDZ until their declaration line.
// Function declarations: fully hoisted ✅const cat = new Animal('Felix'); // Works!function Animal(name) { this.name = name;}
// Class declarations: hoisted but in TDZ ❌const dog = new Dog('Rex'); // ReferenceError: Cannot access 'Dog' before initialization
class Dog { constructor(name) { this.name = name; }}
// Class expressions: same as function expressions — variable is hoisted, class is notconst tiger = new BigCat('Simba'); // TypeError: BigCat is not a constructor
const BigCat = class { constructor(name) { this.name = name; }};Hoisting Order — When Multiple Things Are Declared
When multiple declarations exist in the same scope, they’re processed in a specific order:
// Demonstration of hoisting order
console.log(typeof greet); // 'function' (not 'undefined')
// Function declarations win over var declarations with the same namevar greet = 'hello string'; // ← this var declaration is IGNORED during hoisting// because a function declaration with same name already exists
function greet() { // ← this WINS during compilation return 'hello function';}
console.log(typeof greet); // 'string' — the assignment DID run, overwriting the function// More explicit demonstration// Step 1 (compilation): function declarations are hoisted first// Step 2 (compilation): var declarations follow (but don't overwrite functions)// Step 3 (execution): assignments run top to bottom
var score = 100; // assignment runs: score becomes 100console.log(score); // 100
function score() { // this function is hoisted BEFORE score = 100 runs return 'I am a function';}
// During compilation:// 1. function score() is registered// 2. var score is processed — but since score is already a function, the var declaration is skipped// 3. Execution begins: score = 100 runs (assignment overwrites the function reference)