Hoisting in JavaScript
Hoisting is a phenomena in JavaScript by which you can access variables and function even before you have initialized it or access it without any error.
a()console.log(x)var x = 'kolhe';function a() {console.log('shreyash');}
undefined shreyashJavaScript first declaring the variable and then initializing it. remember the execution context, even before the code starts executing memory is allocated to this all variable and function. means all variables and function store in memory component then it initialized.
yes, it shows undefined. but Why?console.log(x)var x = 'shreyash'
function is a heart of JavaScript, it nothing show an error or not shows undefined because JavaScript function are independent nature. it has not any restriction. when function call it execute the function directly.a();function a(){console.log('shreyash');}
yes it work like function but suppose we call a variable function before initialization. likevar a = () =>{console.log('shreyash');}console.log(a);
so this time, a print undefined because this function is behave or act like a variable. it does not behave like a function. In the memory allocation phase of the execution context it allocate undefine.console.log(a)var a = () => {console.log('shreyash');}
Episode - Hoisting in JavaScript — The Complete Guide
Hey everyone! Welcome back to Namaste JavaScript. Today we are going to deeply understand one of the most confusing yet fundamental concepts in JavaScript — Hoisting!
Most people have heard of hoisting, but very few actually understand what is happening under the hood. By the end of this episode, you will never be confused by hoisting again!
What we will cover:
- What is Hoisting?
- How JavaScript Engine Really Works (Two Phases)
- Variable Hoisting — var, let, const
- Temporal Dead Zone (TDZ)
- Function Declaration Hoisting
- Function Expression and Arrow Function Hoisting
- Class Hoisting
- Hoisting Inside Blocks and Functions
- Order of Precedence in Hoisting
- Common Mistakes and Gotchas
- Famous Interview Questions
What is Hoisting?
You might have heard: "In JavaScript, variables and functions are moved to the top of their scope before execution."
But that is a simplified (and slightly misleading) explanation. Variables and functions are NOT physically moved anywhere. Let me show you what actually happens!
// ❓ QUIZ: What will this print? console.log(x); // ? var x = 10; console.log(x); // ? OUTPUT: undefined 10 // Most people expect an error on line 1. // But JavaScript says: undefined // WHY? Because of HOISTING!
// ❓ QUIZ 2: What about this?
getName(); // ?
console.log(name); // ?
var name = "Shreyesh";
function getName() {
console.log("Hello, JavaScript!");
}
OUTPUT:
Hello, JavaScript!
undefined
// getName() works before it's defined!
// name is undefined, not an error!
To truly understand why this happens, we need to understand how the JavaScript engine actually executes code.
How JavaScript Engine Really Works — Two Phases
JavaScript does NOT run your code line by line in one pass. It runs in TWO phases:
Phase 1: CREATION PHASE (also called Memory Creation Phase) Phase 2: EXECUTION PHASE (also called Code Execution Phase) ┌──────────────────────────────────────────────────────────┐ │ JavaScript Engine │ │ │ │ Phase 1: Creation Phase │ │ ───────────────────────── │ │ → Scans through entire code │ │ → Allocates memory for variables and functions │ │ → var variables: set to undefined │ │ → let/const variables: placed in TDZ (uninitialized) │ │ → Function declarations: FULL function body stored │ │ │ │ Phase 2: Execution Phase │ │ ───────────────────────── │ │ → Runs code line by line │ │ → Assigns actual values to variables │ │ → Executes function calls │ │ │ └──────────────────────────────────────────────────────────┘
Let's trace through an example:
var x = 10;
var y = 20;
function add(a, b) {
return a + b;
}
var result = add(x, y);
─────────────────────────────────────────
PHASE 1: Creation Phase
─────────────────────────────────────────
JavaScript scans ALL the code and sets up memory:
Global Memory:
x → undefined (var → placeholder)
y → undefined (var → placeholder)
add → function add(a, b) { return a + b; } ← FULL body!
result → undefined (var → placeholder)
─────────────────────────────────────────
PHASE 2: Execution Phase (line by line)
─────────────────────────────────────────
Line 1: var x = 10; → x is updated from undefined → 10
Line 2: var y = 20; → y is updated from undefined → 20
Line 3-5: function add → already in memory, skip
Line 6: var result = add(x, y);
→ call add(10, 20) → returns 30
→ result updated from undefined → 30
Final Memory:
x → 10
y → 20
add → function body
result → 30
THIS is what hoisting really is — the Creation Phase allocates memory for everything before execution begins. So when you access a variable or call a function "before" it appears in code, it's already in memory from Phase 1!
Variable Hoisting — var
var variables are hoisted and initialized to undefined during the Creation Phase.
console.log(a); // undefined (NOT an error!) var a = 5; console.log(a); // 5 // What JavaScript ACTUALLY does (after Creation Phase): var a; // ← hoisted to top, initialized to undefined console.log(a); // undefined a = 5; // assignment happens at original line console.log(a); // 5
More Examples: =============== console.log(x + y); // NaN (undefined + undefined = NaN) var x = 10; var y = 20; console.log(x + y); // 30 // Both x and y are undefined during first console.log // undefined + undefined = NaN (not an error!)
var in Functions:
==================
function greet() {
console.log(msg); // undefined (hoisted within function)
var msg = "Hello!";
console.log(msg); // "Hello!"
}
greet();
// var is hoisted to the TOP OF ITS FUNCTION scope!
// NOT to global scope (when inside a function)
var is FUNCTION-SCOPED:
========================
function test() {
if (true) {
var blockVar = "I am var"; // var ignores block scope!
}
console.log(blockVar); // "I am var" ✅ (accessible!)
}
test();
// var leaks out of if-blocks, for-loops, while-loops...
// It only stops at function boundaries!
let and const Hoisting — The Temporal Dead Zone (TDZ)
Here is where most people get confused. let and const ARE hoisted, but they are NOT initialized to undefined. They enter a special state called the Temporal Dead Zone (TDZ).
// ❌ With let: console.log(b); // ReferenceError: Cannot access 'b' before initialization let b = 5; // ❌ With const: console.log(c); // ReferenceError: Cannot access 'c' before initialization const c = 10; // ✅ With var: console.log(a); // undefined (no error!) var a = 7;
What is the Temporal Dead Zone (TDZ)? ======================================= The TDZ is the period between: ← START: when the variable is hoisted (Creation Phase) ← END: when the variable is initialized (Execution Phase reaches declaration) During this period, accessing the variable throws a ReferenceError! ┌──────────────────────────────────────────────────────────┐ │ // Start of scope │ │ │ │ ← b enters TDZ here (Creation Phase) │ │ │ │ console.log(b); ← INSIDE TDZ → ReferenceError! 💥 │ │ console.log(b); ← INSIDE TDZ → ReferenceError! 💥 │ │ │ │ let b = 5; ← TDZ ENDS here, b = 5 ✅ │ │ │ │ console.log(b); ← AFTER TDZ → prints 5 ✅ │ └──────────────────────────────────────────────────────────┘
Proof that let/const ARE hoisted (they just can't be accessed):
=================================================================
let x = "global";
{
console.log(x); // ReferenceError! (NOT "global")
let x = "local";
}
// If 'x' inside the block was NOT hoisted,
// it would print "global" (from outer scope).
// But it throws ReferenceError!
// That means the inner 'x' IS hoisted (knows it exists)
// but is in TDZ (can't be accessed yet)!
TDZ with const:
================
const LIMIT = 100;
function checkTDZ() {
console.log(LIMIT); // ReferenceError! (inner LIMIT is in TDZ)
const LIMIT = 50; // inner LIMIT starts here
console.log(LIMIT); // 50
}
checkTDZ();
// Same as let — const is hoisted but in TDZ until declaration line.
var vs let vs const — Key Differences in Hoisting: ==================================================== ┌──────────────┬───────────────┬──────────────────────────────┐ │ │ HOISTED? │ INITIALIZED TO? │ ├──────────────┼───────────────┼──────────────────────────────┤ │ var │ YES ✅ │ undefined │ │ let │ YES ✅ │ TDZ (uninitialized) 🚫 │ │ const │ YES ✅ │ TDZ (uninitialized) 🚫 │ ├──────────────┼───────────────┼──────────────────────────────┤ │ Before declaration: │ │ │ var │ undefined │ No error │ │ let │ TDZ │ ReferenceError │ │ const │ TDZ │ ReferenceError │ └──────────────┴───────────────┴──────────────────────────────┘
Function Declaration Hoisting
Function declarations are hoisted completely — the entire function body is stored in memory during the Creation Phase. This is why you can call a function before it appears in the code!
// ✅ Works perfectly!
sayHello(); // "Hello!"
greet("Shreyesh"); // "Hello, Shreyesh!"
function sayHello() {
console.log("Hello!");
}
function greet(name) {
console.log("Hello, " + name + "!");
}
// BOTH functions are fully available before their definition!
What Creation Phase stores for function declarations:
======================================================
// Your code:
function add(a, b) {
return a + b;
}
// What gets stored in memory during Phase 1:
add → {
function add(a, b) {
return a + b;
}
}
// The ENTIRE function body — ready to execute!
// For var:
var x = 10;
// What gets stored: x → undefined (just a placeholder)
Function declarations are FULLY hoisted — a key distinction:
=============================================================
console.log(add); // Prints: function add(a, b) { return a + b; }
console.log(x); // Prints: undefined
function add(a, b) { return a + b; }
var x = 10;
// 'add' → the complete function is available immediately!
// 'x' → only undefined (value assigned later in execution phase)
Function Expression and Arrow Function Hoisting
This is where many people make mistakes. Function expressions and arrow functions follow the hoisting rules of the variable they are assigned to!
// ❌ Function Expression with var:
console.log(sayHi); // undefined (NOT the function!)
sayHi(); // TypeError: sayHi is not a function
var sayHi = function() {
console.log("Hi!");
};
// During Creation Phase:
// sayHi → undefined (it's a var!)
// The function body is NOT stored yet!
// sayHi only becomes a function when execution reaches that line.
// ❌ Function Expression with let:
console.log(greet); // ReferenceError: Cannot access 'greet' before initialization
greet(); // ReferenceError
let greet = function() {
console.log("Greet!");
};
// 'greet' is in TDZ — same as any let variable!
// ❌ Arrow Function with var: console.log(add); // undefined add(1, 2); // TypeError: add is not a function var add = (a, b) => a + b; // Arrow functions behave EXACTLY like function expressions // with respect to hoisting!
Summary: ========= Function Declaration: ✅ Fully hoisted (entire body available) Function Expression (var): ⚠️ Hoisted as undefined (TyepError if called) Function Expression (let): ❌ In TDZ (ReferenceError if accessed) Arrow Function (var): ⚠️ Hoisted as undefined (TypeError if called) Arrow Function (let/const): ❌ In TDZ (ReferenceError if accessed)
Class Hoisting
Classes in JavaScript are hoisted but, like let and const, they are placed in the Temporal Dead Zone. You cannot use a class before its declaration!
// ❌ Using class before declaration:
const obj = new MyClass(); // ReferenceError: Cannot access 'MyClass' before initialization
class MyClass {
constructor() {
this.name = "JavaScript";
}
}
// Class is hoisted but in TDZ!
// ✅ Using class after declaration:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
const dog = new Animal("Dog");
dog.speak(); // "Dog makes a sound." ✅
Classes vs Functions — Hoisting Comparison:
============================================
// Function declaration → FULLY hoisted, no TDZ
const obj1 = new MyFunction(); // ✅ Works!
function MyFunction() {}
// Class declaration → Hoisted but in TDZ
const obj2 = new MyClass(); // ❌ ReferenceError!
class MyClass {}
Hoisting Inside Blocks and Functions
Hoisting does NOT always mean "top of file". Variables are hoisted to the top of their scope!
Function Scope Hoisting:
=========================
function outer() {
console.log(a); // undefined (hoisted within outer)
console.log(b); // undefined (hoisted within outer)
var a = 10;
var b = 20;
function inner() {
console.log(a); // undefined (hoisted within inner!)
console.log(b); // 20 (from outer closure — not re-declared!)
var a = 100; // new 'a' local to inner
console.log(a); // 100
}
inner();
console.log(a); // 10 (outer's 'a' unchanged)
}
outer();
Block Scope Hoisting (let/const):
===================================
{
console.log(x); // ReferenceError! (let is in TDZ within this block)
let x = 5;
console.log(x); // 5
}
// let and const are hoisted to the TOP OF THEIR BLOCK!
// (not to function or global scope)
var Leaking Out of Blocks:
===========================
function test() {
console.log(i); // undefined (var is hoisted to function top!)
for (var i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
console.log(i); // 3 (var leaked out of the for loop!)
}
test();
// var is hoisted to the function scope, not the block scope
// It ignores if, for, while blocks completely!
let Staying in Block Scope:
============================
function test() {
// console.log(i); // ← Would be ReferenceError
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
// console.log(i); // ← ReferenceError: i is not defined
}
test();
// let is block-scoped → stays inside the for loop block!
Order of Precedence in Hoisting
When a variable and a function have the same name, which one wins? The answer depends on the order of precedence!
Rule: Function Declaration > var Declaration
=============================================
console.log(foo); // Prints: function foo() {} (function wins!)
var foo = "I am a variable";
function foo() {}
console.log(foo); // "I am a variable" (assignment overrides in execution phase)
// Precedence Order:
// 1. Function declarations (highest priority in hoisting)
// 2. Variable declarations (var — lower priority)
// 3. Assignments happen during execution phase, override hoisting
Multiple Function Declarations — Last One Wins:
================================================
console.log(greet()); // "Second!" (last declaration wins!)
function greet() {
return "First!";
}
function greet() {
return "Second!";
}
// When two function declarations have the same name,
// the LAST one overwrites the first during Creation Phase!
Function Declaration vs Function Expression:
=============================================
console.log(bar); // function bar() { return "declaration"; }
// Function declaration wins over var!
var bar = "variable";
function bar() { return "declaration"; }
// After execution reaches var bar = "variable":
console.log(bar); // "variable"
// Assignment overrides the function reference!
Complete Precedence Order: =========================== 1. Function declarations (hoisted first, fully) 2. Variable declarations (var → undefined) 3. let / const declarations (hoisted but TDZ) 4. Assignments (only happen during execution phase) When names collide: Function declaration > var declaration (in hoisting phase) Last function declaration > earlier function declarations Assignment always wins at execution time (overwrites hoisted value)
Common Mistakes and Gotchas
Gotcha 1: var Inside if Block
// ❌ Surprising behavior with var:
if (false) {
var surprise = "I exist!";
}
console.log(surprise); // undefined (NOT a ReferenceError!)
// var is hoisted to function/global scope even if INSIDE if(false)!
// The code inside if(false) never runs,
// but var declaration is still hoisted!
// Creation Phase scans ALL code — even dead branches!
// It sees 'var surprise' and hoists it as undefined.
// The assignment ("I exist!") never runs → stays undefined.
Gotcha 2: Function Inside if Block
// ⚠️ Non-strict mode — behavior differs across environments:
if (true) {
function blockFunc() {
return "inside if-true";
}
}
if (false) {
function blockFunc() {
return "inside if-false";
}
}
console.log(blockFunc()); // "inside if-true" (or undefined in some environments)
// Function declarations inside blocks are NOT reliably hoisted!
// Behavior is different in strict mode vs non-strict mode.
// AVOID putting function declarations inside if/else blocks!
// Use function expressions instead:
let blockFunc;
if (true) {
blockFunc = function() { return "inside if-true"; };
}
Gotcha 3: Hoisting in Switch Statements
// var is hoisted across ALL cases in a switch!
switch (value) {
case 1:
var result = "one";
break;
case 2:
var result = "two"; // Same 'result' variable as case 1!
break;
}
console.log(result); // "one" or "two" depending on value
// All 'var result' declarations refer to the SAME variable!
// Use let inside switch with block scoping:
switch (value) {
case 1: {
let result = "one"; // Scoped to this block!
break;
}
case 2: {
let result = "two"; // Different variable!
break;
}
}
Gotcha 4: Hoisting with typeof
// typeof is SAFE to use even in TDZ... for undeclared variables: console.log(typeof undeclaredVar); // "undefined" (no error!) // But NOT for let/const in TDZ: console.log(typeof myLet); // ReferenceError! 💥 let myLet = 5; // typeof does NOT protect you from TDZ errors!
Gotcha 5: The Loop Variable Classic Problem
// ❌ Classic problem with var in loops:
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push(function() {
return i; // All functions close over the SAME 'i'!
});
}
console.log(funcs[0]()); // 3 (not 0!)
console.log(funcs[1]()); // 3 (not 1!)
console.log(funcs[2]()); // 3 (not 2!)
// Because var i is hoisted to function scope,
// all three closures reference the same 'i',
// which is 3 after the loop ends.
// ✅ Fix with let:
var funcs2 = [];
for (let i = 0; i < 3; i++) {
funcs2.push(function() {
return i; // Each function closes over its OWN 'i'!
});
}
console.log(funcs2[0]()); // 0 ✅
console.log(funcs2[1]()); // 1 ✅
console.log(funcs2[2]()); // 2 ✅
Strict Mode and Hoisting
"use strict";
// 1. Undeclared variables are NOT allowed:
x = 10; // ReferenceError: x is not defined
// (in non-strict mode, this would create a global var!)
// 2. Duplicate function parameters not allowed:
function sum(a, a) { } // SyntaxError in strict mode
// 3. Function declarations in blocks are block-scoped in strict mode:
"use strict";
if (true) {
function hello() { return "hi"; }
}
// hello(); // ReferenceError in strict mode! (block-scoped)
// Works in non-strict mode (leaked to function scope)
// Best Practice: Always use "use strict" or ES6 modules (auto-strict)!
Hoisting in Real-World Code
Real-World Example 1: Using Functions Before Defining
// ✅ This works because of hoisting — code reads top to bottom logically:
init(); // Call setup at top — works!
function init() {
loadConfig();
setupDatabase();
startServer();
}
function loadConfig() {
console.log("Loading config...");
}
function setupDatabase() {
console.log("Connecting to database...");
}
function startServer() {
console.log("Server started on port 3000");
}
// Function declarations let you organize code
// with higher-level functions at the TOP and
// implementation details at the BOTTOM — very readable!
Real-World Example 2: The Danger of var in Async Code
// ❌ Classic async + var hoisting bug:
function fetchAll(ids) {
for (var i = 0; i < ids.length; i++) {
fetch('/api/item/' + ids[i])
.then(function(response) {
console.log('Fetched item at index:', i);
// By the time fetch completes, the loop is done!
// 'i' is now ids.length for ALL callbacks!
});
}
}
// ✅ Fix with let:
function fetchAll(ids) {
for (let i = 0; i < ids.length; i++) {
fetch('/api/item/' + ids[i])
.then(function(response) {
console.log('Fetched item at index:', i);
// Each callback has its own 'i' — correct!
});
}
}
Real-World Example 3: Avoiding TDZ with Proper Declaration Order
// ❌ Bad practice — accessing before declaring:
function processUser() {
console.log(userName); // ReferenceError (TDZ!)
let userName = getUserName();
return userName.toUpperCase();
}
// ✅ Good practice — declare before use:
function processUser() {
let userName = getUserName();
console.log(userName); // "john" ✅
return userName.toUpperCase();
}
// TDZ errors are JavaScript's way of HELPING you write
// predictable, readable code — declare before use!
Interview Questions
Q1: What is Hoisting in JavaScript?
"Hoisting is JavaScript's behavior of moving variable and function declarations to the top of their scope during the Creation Phase of the execution context. The JavaScript engine runs in two phases — Creation Phase (where memory is allocated for variables and functions) and Execution Phase (where code runs line by line). So when we say 'hoisting', we mean the engine has already set up memory for our identifiers before execution begins."
Q2: What is the difference between how var, let, and const are hoisted?
"All three are hoisted, but differently. var is hoisted and initialized to undefined — you can access it before its declaration and get undefined. let and const are hoisted but placed in the Temporal Dead Zone (TDZ) — accessing them before their declaration throws a ReferenceError. The TDZ exists from when the variable is hoisted until the line where it's declared."
Q3: What is the Temporal Dead Zone (TDZ)?
"The TDZ is the period between when a let or const variable is hoisted (start of its scope) and when it's actually initialized (its declaration line). During this period, the variable exists in memory but cannot be accessed — any attempt throws a ReferenceError saying 'Cannot access variable before initialization'. The TDZ encourages declaring variables before using them."
Q4: Are function expressions hoisted?
"Function expressions follow the hoisting behavior of the variable they are assigned to, not function hoisting rules. If assigned to var, the variable is hoisted as undefined — calling it before the assignment throws TypeError: not a function. If assigned to let or const, it's in TDZ and throws ReferenceError. Only function declarations are fully hoisted with their entire body."
Q5: What is the output of this code?
console.log(typeof foo); // ?
console.log(typeof bar); // ?
console.log(typeof baz); // ?
var foo = 1;
let bar = 2;
function baz() {}
"typeof foo → 'undefined' (var is hoisted as undefined). typeof bar → ReferenceError! (let is in TDZ, typeof does NOT protect from TDZ). typeof baz → 'function' (function declaration is fully hoisted)."
Q6: Which wins in a name conflict — var or function declaration?
"Function declarations have higher priority than var declarations during hoisting. So if both have the same name, the function wins initially. However, during execution, if an assignment occurs for the var, it overrides the function reference. If there are multiple function declarations with the same name, the last one wins."
Q7: Can you explain the two phases of JavaScript execution?
"Phase 1 — Creation Phase: The engine scans all code and allocates memory. For var, it sets aside space and initializes to undefined. For let/const, it allocates space but marks them as uninitialized (TDZ). For function declarations, it stores the entire function body. Phase 2 — Execution Phase: The engine runs code line by line, assigns values to variables, and executes function calls. This two-phase process is what causes hoisting behavior."
Q8: What happens when you declare a var inside an if(false) block?
"The var is still hoisted to the enclosing function/global scope with value undefined. The Creation Phase scans ALL code regardless of whether conditions are true or false — it doesn't execute code, it just sets up memory. So var declarations inside if(false) blocks are hoisted and initialized to undefined, even though the assignment never runs."
Quick Recap
| Type | Hoisted? | Initialized To | Scope | Access Before Declaration |
|---|---|---|---|---|
| var | YES | undefined | Function / Global | Returns undefined |
| let | YES | TDZ (uninitialized) | Block | ReferenceError |
| const | YES | TDZ (uninitialized) | Block | ReferenceError |
| Function Declaration | YES (fully) | Entire function body | Function / Global | Works perfectly ✅ |
| Function Expression (var) | YES (as var) | undefined | Function / Global | TypeError (not a function) |
| Function Expression (let) | YES (as let) | TDZ | Block | ReferenceError |
| Arrow Function (var) | YES (as var) | undefined | Function / Global | TypeError (not a function) |
| Class | YES | TDZ (uninitialized) | Block | ReferenceError |
Key Points to Remember
- Hoisting is NOT physical movement — it's memory allocation in the Creation Phase
- JavaScript runs in two phases: Creation Phase → Execution Phase
- var → hoisted and initialized to undefined
- let and const → hoisted but in Temporal Dead Zone (TDZ)
- Function Declarations → fully hoisted (entire body available)
- Function Expressions and Arrow Functions → hoisted as their variable type
- Classes → hoisted but in TDZ (like let/const)
- TDZ = period between hoisting and initialization → ReferenceError if accessed
- typeof does NOT protect you from TDZ errors (unlike undeclared variables)
- var leaks out of blocks (if, for, while) — stopped only by function boundaries
- Function Declaration > var declaration in hoisting priority
- Last function declaration wins when names collide
- var in loops + async code = classic bug! Use let to fix it
- Best practice: Always declare before use — avoid relying on hoisting
- Use "use strict" to prevent accidental global variable creation
Keep coding, keep learning! See you in the next one!
Post a Comment