Hoisting - The Simple Rules

What it does: JavaScript moves all var, function, and declaration statements to the top of their scope during compilation - but only the declarations, not the values.

Why Hoisting Exists:

JavaScript engine does two passes:

  1. Compilation pass: Scan for all declarations, allocate memory
  2. Execution pass: Actually run the code

This lets you use functions before you declare them (which feels natural) but creates weird behavior with variables.

The Hoisting Hierarchy:

1. Function Declarations (Full Hoisting)

console.log(sayHi()); // "Hello!" - Works perfectly
function sayHi() { return "Hello!"; }

2. var Variables (Declaration Only)

console.log(x); // undefined (not error!)
var x = 5;
console.log(x); // 5

3. let/const (Temporal Dead Zone)

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

Interview Gotchas:

Gotcha 1: var vs function with same name

console.log(foo); // function foo() { return "function"; }
var foo = "variable";
function foo() { return "function"; }
console.log(foo); // "variable"
// Functions win over var declarations!

Gotcha 2: Function expressions don’t hoist

console.log(myFunc); // undefined
console.log(myFunc()); // TypeError: myFunc is not a function
var myFunc = function() { return "Hello"; };

Gotcha 3: The classic for-loop trap

for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100); // 3, 3, 3
}
// var hoisted to function scope, all timeouts see final value

Gotcha 4: Temporal Dead Zone confusion

javascript

function example() {
    console.log(x); // ReferenceError (not undefined!)
    let x = 1;
}

The Mental Model:

What JavaScript actually sees:

How to Always Know the Way Out:

Rule 1: Declarations vs Initializations

  • Declarations get hoisted: var x, function foo(),
  • Initializations stay put: x = 5, y = 10

Rule 2: The Hoisting Order

  1. Function declarations (fully hoisted)
  2. var (declaration only, initialized as undefined)
  3. let/const (hoisted but in temporal dead zone)

Rule 3: Scope Boundaries

  • Everything hoists to the top of its scope (function/block)
  • Not to global scope unless already in global scope

Safe Practice:

javascript

// Always declare at the top to match hoisting behavior
function myFunction() {
    var x, y; // Declare first
    let a, b; // Block-scoped declarations
    
    // Then your logic
    x = getValue();
    a = getOtherValue();
}

Bottom line: Hoisting moves declarations (not values) to the top of scope. Functions win over vars, but let/const throw errors if used before declaration. Always declare variables at the top of their scope to avoid confusion.


Phase 1 (Compilation):

javascript

let name = 20;
// JavaScript says: "I see 'name' label, reserve memory address 0x200"
// Ignores the "= 20" part completely

Phase 2 (Execution):

javascript

// Now goes line by line, assigns values and executes
name = 20; // NOW it puts 20 in that memory address

”Moving Up” is Just Our Way of Explaining:

Exactly! Nothing physically moves. JavaScript just acts like they’re at the top because it already knows about them from Phase 1.

Function Priority - You’re Right:

Hoisting Order in Memory:

  1. Function declarations (fully loaded)

  2. var declarations (undefined)

  3. let/const declarations (in TDZ)Two ways to explain TDZ:

  4. Simple: “let/const are hoisted but you can’t access them until declaration line”

  5. Technical: “They’re in a separate memory space, not accessible in current scope until initialization”

Both are correct! Use whichever makes more sense to you.


Why Function Wins Over var Declaration:

The Rule: When JavaScript sees same name:

  1. Function declaration creates the variable AND assigns the function
  2. var declaration only tries to create variable (but it already exists!)
  3. var assignment overwrites whatever was there

Step by Step:

javascript

// Step 1: Function declaration
function foo() { return "I win!"; }
// foo now contains: function foo() { return "I win!"; }
 
// Step 2: var declaration  
var foo; 
// JavaScript says: "foo already exists, skip this"
// foo still contains: function foo() { return "I win!"; }
 
// Step 3: Read value
console.log(foo); // Gets current value → function!
 
// Step 4: Assignment
foo = "variable";
// NOW foo contains: "variable"

The Mental Model:

Think of it like a box:

  • Function declaration: Puts function in the box
  • var declaration: Tries to put undefined in box (but if something’s already there, does nothing)
  • Assignment: Replaces whatever’s in the box

javascript

// Box starts empty
function foo() {...} // Box now has: function
var foo;             // Box already has something, skip
console.log(foo);    // Read from box: function
foo = "variable";    // Replace box contents: "variable"

Why This Happens:

Priority Order in Memory:

  1. Function declarations (create variable + assign function)
  2. var declarations (create variable + assign undefined, but only if doesn’t exist)
  3. Assignments happen during execution

Bottom line: Functions don’t just get “priority” - they actually CREATE and FILL the variable. var declarations can’t overwrite existing variables, only assignments can. That’s why the function survives until the assignment line.