DEV Community

Cover image for 🧠 JavaScript Hoisting
Vijay Thopate
Vijay Thopate

Posted on

🧠 JavaScript Hoisting

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
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

Output:

Hello
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Output:

undefined
Enter fullscreen mode Exit fullscreen mode

Memory phase:

a = undefined
Enter fullscreen mode Exit fullscreen mode

Execution phase:

a = 10
Enter fullscreen mode Exit fullscreen mode

This often leads to subtle bugs.


🔒 let & const — Hoisted but NOT initialized (TDZ)

console.log(x);
let x = 10;
Enter fullscreen mode Exit fullscreen mode

Throws:

ReferenceError
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

Output:

10
Enter fullscreen mode Exit fullscreen mode

x escaped the block 😬

Because:

var ignores { } blocks and lives across the function


🟢 let & const are Block-Scoped

function test() {
  if (true) {
    let y = 20;
  }
  console.log(y);
}
test();
Enter fullscreen mode Exit fullscreen mode

Output:

ReferenceError
Enter fullscreen mode Exit fullscreen mode

Behavior is predictable & safer.


🧨 Real-World Bug — Why var is Discouraged

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i));
}
Enter fullscreen mode Exit fullscreen mode

Output:

3
3
3
Enter fullscreen mode Exit fullscreen mode

Because:

  • var creates one shared i
  • loop finishes
  • async callbacks run later
  • i = 3

let Fixes It

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i));
}
Enter fullscreen mode Exit fullscreen mode

Output:

0
1
2
Enter fullscreen mode Exit fullscreen mode

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() {}
Enter fullscreen mode Exit fullscreen mode

Works.


Function Expression (NOT hoisted as function)

greet();
var greet = function() {};
Enter fullscreen mode Exit fullscreen mode

Output:

TypeError: greet is not a function
Enter fullscreen mode Exit fullscreen mode

Because:

  • greet is hoisted as undefined
  • function assigned later

Arrow Functions behave the same

sayHi();
const sayHi = () => {};
Enter fullscreen mode Exit fullscreen mode

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)