Most developers hear “hoisting” and think:
“Variables get moved to the top.”
But that’s not what really happens.
Hoisting is about how JavaScript allocates memory before execution, and how variables and functions behave differently during that phase.
Let’s break it down in a simple, intuitive, story-driven way — and then connect it to how the JavaScript engine actually works.
🎯 What is Hoisting?
Hoisting is the process where JavaScript:
1️⃣ scans your code before execution
2️⃣ allocates memory for variables & functions
3️⃣ sets initial values (depending on keyword)
Execution happens only after this.
Nothing is physically “moved”.
It only appears that way.
🏫 Real-Life Analogy #1 — Classroom Attendance Register
Before teaching begins, a teacher:
✔ writes all students’ names in the attendance register
❌ but attendance is not yet marked
Names exist — but values are empty.
Later during class:
✔ attendance is marked
✔ details are filled in
That is exactly how hoisting works.
| Stage | Classroom | JavaScript |
|---|---|---|
| Before class | write names | store variables & functions in memory |
| During class | mark attendance | assign values during execution |
So when a name is called early,
it exists — but may not have a value yet.
That’s why var becomes undefined.
🍽 Real-Life Analogy #2 — Restaurant Kitchen Plates
Before customers arrive:
✔ plates are kept on tables
✔ table numbers are assigned
❌ food is not yet served
Plates exist — but are empty.
Same with var:
Variable exists → but value is undefined
let and const are like plates that are:
🔒 covered until the right time (TDZ)
We’ll come back to this.
⚙️ How JavaScript Executes Code (2-Phase Model)
Whenever JS runs your code, it creates:
1️⃣ Memory Creation Phase (Hoisting Phase)
- function declarations stored fully
- variables allocated in memory
2️⃣ Execution Phase
- code runs line-by-line
- values get assigned
- functions get executed
🧩 What Actually Gets Hoisted?
Let’s see behavior for each keyword.
✅ Function Declarations — Fully Hoisted
sayHi();
function sayHi() {
console.log("Hello");
}
Output:
Hello
Because:
✔ function body is stored in memory
✔ can be called before declaration
This is the useful side of hoisting.
⚠️ var — Hoisted but Initialized as undefined
console.log(a);
var a = 10;
Output:
undefined
Memory phase:
a = undefined
Execution phase:
a = 10
This often leads to subtle bugs.
🔒 let & const — Hoisted but NOT initialized (TDZ)
console.log(x);
let x = 10;
Throws:
ReferenceError
Because variable is in
🟡 Temporal Dead Zone (TDZ)
Meaning:
variable exists in memory
but cannot be accessed until declared
This behavior makes code safer.
🧱 Function-Scoped vs Block-Scoped (Why var Causes Bugs)
🟡 var is Function-Scoped
function test() {
if (true) {
var x = 10;
}
console.log(x);
}
test();
Output:
10
x escaped the block 😬
Because:
varignores{ }blocks and lives across the function
🟢 let & const are Block-Scoped
function test() {
if (true) {
let y = 20;
}
console.log(y);
}
test();
Output:
ReferenceError
Behavior is predictable & safer.
🧨 Real-World Bug — Why var is Discouraged
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i));
}
Output:
3
3
3
Because:
-
varcreates one sharedi - loop finishes
- async callbacks run later
i = 3
let Fixes It
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i));
}
Output:
0
1
2
Because:
✔ new i is created per iteration
✔ each callback receives correct value
🧠 Why let & const Were Introduced
They were added to:
✔ prevent hidden hoisting bugs
✔ avoid accidental redeclaration
✔ provide predictable block scope
✔ fix async & loop bugs
🟡 Why TDZ Was Introduced
TDZ forces:
“Declare variable before using it.”
This prevents:
❌ silent undefined behavior
❌ accidental access before initialization
❌ hard-to-debug runtime errors
Instead — JavaScript loudly warns you 👍
🧪 Function Declarations vs Function Expressions
Function Declaration (Hoisted)
hello();
function hello() {}
Works.
Function Expression (NOT hoisted as function)
greet();
var greet = function() {};
Output:
TypeError: greet is not a function
Because:
-
greetis hoisted asundefined - function assigned later
Arrow Functions behave the same
sayHi();
const sayHi = () => {};
ReferenceError (TDZ)
📌 Summary Table — What Gets Hoisted?
| Type | Hoisted? | Initial Value | Safe? |
|---|---|---|---|
| function declaration | ✅ yes | full function | 👍 useful |
| var | ✅ yes | undefined | ⚠️ risky |
| let | ✅ yes | TDZ (not initialized) | ✅ safe |
| const | ✅ yes | TDZ + must assign | ✅ safe |
| function expression | ⚠️ variable only | undefined | ❌ |
| arrow function | ⚠️ variable only | TDZ | ❌ |
🧠 Key Takeaways
Hoisting exists because JS engine:
✔ prepares memory before execution
✔ stores declarations first
✔ executes later
Function hoisting is useful.
var hoisting is confusing.
Modern JavaScript prefers:
✔ let for variables
✔ const for constants
✔ declare before using
Because clean code > clever tricks.
✍️ Closing Thought
Hoisting is not a magical behavior.
It is simply how JavaScript:
builds memory first
and runs code afterward
Once you understand:
- execution context
- scope
- TDZ
- call stack
JavaScript stops feeling surprising —
and starts feeling logical.
Top comments (0)