Skip to main content

Hoisting

It's a mechanism where variables and function declarations are moved to the top of their containing scope during the compile phase, before the code is executed. This means that you can use functions and variables before they are declared in the code.

However, only declarations are hoisted, not initializations. The declaration is moved to the top, but the assignment or initialization stays in its place.

Types of Hoisting

1.Variable Hoisting:

  • Variables declared with var are hoisted to the top of their scope but are initialized with undefined until they are assigned a value.

  • let and const declarations are hoisted but are not initialized. They are in a "temporal dead zone" (TDZ) from the start of the block until the declaration is encountered. 1.Variable Hoisting:

  • Variables declared with var are hoisted to the top of their scope but are initialized with undefined until they are assigned a value.

  • let and const declarations are hoisted but are not initialized. They are in a "temporal dead zone" (TDZ) from the start of the block until the declaration is encountered.

2.Function Hoisting:

  • Function declarations are fully hoisted. This means you can call the function even before it is declared in the code.

  • Function expressions assigned to variables (using var, let, or const) are not hoisted in the same way as function declarations 2.Function Hoisting:

  • Function declarations are fully hoisted. This means you can call the function even before it is declared in the code.

  • Function expressions assigned to variables (using var, let, or const) are not hoisted in the same way as function declarations

greet(); // Output: Hello, World!

function greet() {
console.log("Hello, World!");
}

Function Expression:

Function expressions are not fully hoisted, because variable stores undefined at creation phase. It stored the function body during execution phase. It's just an another variable assigned undefined. That's why calling something undefined will cause an error. We can use it as variable that will show undefined but will not cause an error.

Function expressions are not fully hoisted, because variable stores undefined at creation phase. It stored the function body during execution phase. It's just an another variable assigned undefined. That's why calling something undefined will cause an error. We can use it as variable that will show undefined but will not cause an error.

sayHello(); // TypeError: sayHello is not a function

var sayHello = function () {
console.log("Hello!");
};

How IIFE Interacts With Hoisting

How IIFE Interacts With Hoisting

Step 1: Function Expression vs Function Declaration

Step 1: Function Expression vs Function Declaration

  • Function declaration (function foo(){}) is hoisted.
  • Function expression (const foo = function(){} or (function(){})) is not hoisted in the same way — it behaves like a variable assignment.
  • Function declaration (function foo(){}) is hoisted.
  • Function expression (const foo = function(){} or (function(){})) is not hoisted in the same way — it behaves like a variable assignment.
foo(); // Works
function foo() {
console.log("declaration");
}

bar(); // ❌ TypeError: bar is not a function
var bar = function () {
console.log("expression");
};

So in an IIFE, since it’s a function expression, only the variable (if assigned) is hoisted, not the function body.

Step 2: IIFE Creates a New Scope

When an IIFE runs:

  • It creates a new execution context (like calling any function).
  • Inside it, hoisting rules apply again (vars hoisted to function scope, let/const in TDZ).
  • This protects outer/global scope from variable pollution.
foo(); // Works
function foo() {
console.log("declaration");
}

bar(); // ❌ TypeError: bar is not a function
var bar = function () {
console.log("expression");
};

So in an IIFE, since it’s a function expression, only the variable (if assigned) is hoisted, not the function body.

Step 2: IIFE Creates a New Scope

When an IIFE runs:

  • It creates a new execution context (like calling any function).
  • Inside it, hoisting rules apply again (vars hoisted to function scope, let/const in TDZ).
  • This protects outer/global scope from variable pollution.
var x = 1;

(function () {
console.log(x); // undefined (var is hoisted inside IIFE)
var x = 2;
console.log(x); // 2
})();

console.log(x); // 1 (outer scope unaffected)

Inside the IIFE, var x is hoisted to the top of the IIFE’s scope, shadowing the global x.

Step 3: IIFE and let/const

Using let/const inside an IIFE prevents leakage into the global scope, and they are still hoisted into the IIFE’s scope but in TDZ.

(function () {
// console.log(y); // ❌ ReferenceError (TDZ)
let y = 10;
console.log(y); // 10
})();

// console.log(y); // ❌ ReferenceError (not defined outside IIFE)

Step 4: Named IIFE Hoisting

IIFEs can be named, but the name is scoped only inside the IIFE.

(function greet() {
console.log("Hello!");
// console.log(greet); // function reference available inside
})();

// console.log(greet); // ❌ ReferenceError (not defined outside)

Here, greet is hoisted inside the IIFE only, not globally.

Key Points about hoisting with IIFE

  1. IIFE itself is a function expression → not hoisted like declarations.
  • You can’t call it before it appears.
  1. Inside IIFE, normal hoisting rules apply:
  • var hoisted (initialized undefined).
  • function declarations fully hoisted.
  • let/const hoisted into TDZ.
  1. IIFE provides a scope barrier → prevents hoisted vars/functions from leaking into global scope.

How Redeclaration affect

Function Scope

  • var declarations are function-scoped (or global if outside a function).
  • Hoisting behavior: The variable is hoisted to the top of its scope and initialized with undefined.
  • Because of this, JavaScript allows redeclaration in the same scope (it just ignores the second declaration).

Block Scope

  • let is block-scoped (only visible inside { ... }).
  • Variables in a block are in a temporal dead zone (TDZ) until initialized.
  • Redeclaration in the same block is forbidden, because JS must avoid ambiguity about which variable is being referenced.
  • This is a compile-time error.
  • TDZ + block scoping ensures safer, predictable behavior.