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:
- Compilation pass: Scan for all declarations, allocate memory
- 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); // 53. 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 valueGotcha 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
- Function declarations (fully hoisted)
- var (declaration only, initialized as undefined)
- 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 completelyPhase 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:
-
Function declarations (fully loaded)
-
var declarations (undefined)
-
let/const declarations (in TDZ)Two ways to explain TDZ:
-
Simple: “let/const are hoisted but you can’t access them until declaration line”
-
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:
- Function declaration creates the variable AND assigns the function
- var declaration only tries to create variable (but it already exists!)
- 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:
- Function declarations (create variable + assign function)
- var declarations (create variable + assign undefined, but only if doesn’t exist)
- 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.
