Real-Life Example of Hoisting
Imagine you’re in a classroom, and your teacher says, “You can ask me questions about Chapter 5, but first, let me explain Chapter 5 in detail.”
In this scenario, even before the explanation of Chapter 5 starts, you know the topic exists and can refer to it, but you won’t have its full details until the teacher explains it. Similarly, in JavaScript, variables and functions are “hoisted” to the top of their scope, so JavaScript knows they exist, but their actual value or implementation is only accessible once the code execution reaches that part.
What is Hoisting?
Hoisting is a JavaScript mechanism where variable, function and class declarations are moved to the top of their scope during the compilation phase. This allows us to use them before they are declared in the code. However, only the declarations are hoisted, not the initializations.
Comprehensive Examples of Hoisting
1. Variable Hoisting Examples
var Hoisting Examples
Example 1: Basic var hoisting
console.log(myVar); // Output: undefined
var myVar = 'Hello';
console.log(myVar); // Output: HelloThis is the most basic example of var hoisting. The variable myVar is declared with var, so its declaration is hoisted to the top of the scope and initialized to undefined. When we try to access it before the assignment, we get undefined, not a ReferenceError.
Example 2: var hoisting in function scope
function testVarHoisting() {
console.log(innerVar); // Output: undefined
var innerVar = 'Function scoped';
console.log(innerVar); // Output: Function scoped
}
testVarHoisting();Variables declared with var inside a function are hoisted to the top of that function's scope. The variable innerVar is accessible throughout the entire function, even before its declaration line.
Example 3: var hoisting with multiple declarations
console.log(multipleVar); // Output: undefined
var multipleVar = 'First assignment';
var multipleVar = 'Second assignment'; // This overwrites the first
console.log(multipleVar); // Output: Second assignmentYou can declare the same variable multiple times with var. The first declaration is hoisted, and subsequent declarations are ignored, but assignments still work. The final value is the last assignment.
Example 4: var hoisting in blocks (function-scoped, not block-scoped)
if (true) {
var blockVar = 'I am accessible outside the block';
}
console.log(blockVar); // Output: I am accessible outside the blockVariables declared with var are function-scoped, not block-scoped. Even though blockVaris declared inside an if block, it's accessible outside the block because it's hoisted to the function scope (or global scope if not in a function).
Example 5: var hoisting with conditional statements
console.log(conditionalVar); // Output: undefined
if (true) {
var conditionalVar = 'Conditionally assigned';
}
console.log(conditionalVar); // Output: Conditionally assignedThe declaration of conditionalVar is hoisted regardless of whether the if condition is true or false. The variable exists in the scope before the conditional block, but its assignment only happens if the condition is met.
Example with `let & const`: What is Temporal Dead Zone (TDZ)?
In JavaScript, the Temporal Dead Zone (TDZ) refers to the period within a block scope where variables declared with let or const are in a hoisted state but not yet initialized. During this phase, accessing these variables results in a ReferenceError.
Or In Simple TermA temporal dead zone (TDZ) is the area of a block where a variable is inaccessible until the moment the JavaScript engine completely initializes it with a value.
Let's understand with a example
{
// favoriteDish’s TDZ starts here (at the beginning of this block’s local scope)
// favoriteDish’s TDZ continues here
// favoriteDish’s TDZ continues here
// favoriteDish’s TDZ continues here
console.log(favoriteDish); // ReferenceError: Cannot access 'favoriteDish' before initialization
// favoriteDish’s TDZ continues here
// favoriteDish’s TDZ continues here
let favoriteDish = "Pizza"; // favoriteDish’s TDZ ends here
// favoriteDish’s TDZ does not exist here
// favoriteDish’s TDZ does not exist here
// favoriteDish’s TDZ does not exist here
}Variables declared with let are hoisted but not initialized. They remain in the Temporal Dead Zone until the actual declaration line is reached. Attempting to access them before initialization results in a ReferenceError, not undefined.
Example 2: const hoisting - TDZ demonstration
console.log(constVar); // ReferenceError: Cannot access 'constVar' before initialization
const constVar = 'I am also hoisted but in TDZ';Like let, variables declared with const are also hoisted but remain in the Temporal Dead Zone until initialization. The key difference is that const must be initialized with a value and cannot be reassigned later.
Example 3: let hoisting in function scope
function testLetHoisting() {
console.log(functionLet); // ReferenceError: Cannot access 'functionLet' before initialization
let functionLet = 'Function scoped let';
console.log(functionLet); // Output: Function scoped let
}
// testLetHoisting(); // This will throw an errorEven inside a function, let variables are hoisted but remain in the TDZ until their declaration line. The variable is accessible throughout the function after its declaration, but not before.
Example 4: let hoisting in block scope
if (true) {
console.log(blockLet); // ReferenceError: Cannot access 'blockLet' before initialization
let blockLet = 'Block scoped let';
console.log(blockLet); // Output: Block scoped let
}
// console.log(blockLet); // ReferenceError: blockLet is not defined (block-scoped)let variables are block-scoped, meaning they're only accessible within the block where they're declared. They're hoisted to the top of their block scope but remain in the TDZ until initialization.
Example 5: const hoisting with objects
console.log(constObj); // ReferenceError: Cannot access 'constObj' before initialization
const constObj = { name: 'John', age: 30 };
console.log(constObj.name); // Output: JohnWhen const is used with objects, the variable itself cannot be reassigned, but the object's properties can still be modified. The hoisting behavior is the same as other const declarations.
Example 6: let hoisting in loops
for (let i = 0; i < 3; i++) {
console.log(i); // Output: 0, 1, 2
}
// console.log(i); // ReferenceError: i is not defined (block-scoped)Loop variables declared with let are block-scoped to the loop body. Each iteration gets its own variable instance, and the variable is not accessible outside the loop block.
Example 7: const hoisting with arrays
console.log(constArray); // ReferenceError: Cannot access 'constArray' before initialization
const constArray = [1, 2, 3, 4, 5];
console.log(constArray[0]); // Output: 1Similar to objects, const arrays cannot be reassigned, but their elements can be modified. The hoisting behavior follows the same TDZ rules as other const declarations.
2. Function Hoisting Examples
Function Declaration Hoisting
Example 1: Basic function declaration hoisting
sayHello(); // Output: Hello!
function sayHello() {
console.log('Hello!');
}Function declarations are fully hoisted, meaning both the declaration and the implementation are moved to the top of their scope. This allows you to call a function before it appears in the code.
Example 2: Function declaration hoisting in nested scopes
outerFunction(); // Output: Outer function called
function outerFunction() {
console.log('Outer function called');
innerFunction(); // Output: Inner function called
function innerFunction() {
console.log('Inner function called');
}
}Both outer and inner function declarations are hoisted. The outer function can be called before its declaration, and the inner function is hoisted within the outer function's scope, making it available throughout the outer function.
Example 3: Function declaration hoisting with parameters
greet('John'); // Output: Hello, John!
function greet(name) {
console.log('Hello, ' + name + '!');
}Function declarations with parameters are also fully hoisted. The entire function, including its parameter list and implementation, is available before the declaration line.
Example 4: Function declaration hoisting with return values
const result = multiply(5, 3); // Output: 15
console.log(result);
function multiply(a, b) {
return a * b;
}Functions that return values are also fully hoisted. You can call the function and use its return value before the function declaration appears in the code.
Function Expression Hoisting
Example 1: Function expression with var (not hoisted)
sayHi(); // TypeError: sayHi is not a function
var sayHi = function() {
console.log('Hi there!');
};Function expressions assigned to var variables are not hoisted. Only the variable declaration is hoisted (initialized to undefined), but the function assignment happens at runtime. Calling the function before assignment results in a TypeError.
Example 2: Function expression with let (not hoisted)
greet(); // ReferenceError: Cannot access 'greet' before initialization
let greet = function() {
console.log('Greetings!');
};Function expressions assigned to let variables are not hoisted. The variable is in the Temporal Dead Zone until the assignment line is reached, so calling it before assignment results in a ReferenceError.
Example 3: Function expression with const (not hoisted)
welcome(); // ReferenceError: Cannot access 'welcome' before initialization
const welcome = function() {
console.log('Welcome!');
};Function expressions assigned to const variables behave the same as let. They're not hoisted and remain in the TDZ until initialization, resulting in a ReferenceError if called before assignment.
Example 4: Arrow function expression (not hoisted)
arrowGreet(); // ReferenceError: Cannot access 'arrowGreet' before initialization
const arrowGreet = () => {
console.log('Arrow function greeting!');
};Arrow functions are function expressions and follow the same hoisting rules. They're not hoisted and must be declared before they can be called. The arrow syntax doesn't change the hoisting behavior.
Example 5: Function expression with parameters
calculate(); // TypeError: calculate is not a function
var calculate = function(a, b) {
return a + b;
};Function expressions with parameters are not hoisted either. The function definition, including its parameters, is only available after the assignment line is executed.
Hoisting Priority Rules
One common interview question is: what happens if a function and a variable have the same name?In JavaScript, function declarations get higher priority during hoisting than var declarations.
Case 1: Same function name and var name
console.log(test); // Output: function test() { return "I am function"; }
var test = "I am variable";
function test() {
return "I am function";
}
console.log(test); // Output: I am variableHere is what happens:
- The function declaration is hoisted first with its full body.
- The
var testdeclaration is also hoisted, but only as a declaration. - Since the function already owns that name, the
vardeclaration does not replace it during hoisting. - Later, when execution reaches
test = "I am variable", the variable assignment overrides the function value.
Mental model
// JavaScript treats it roughly like this:
function test() {
return "I am function";
}
var test; // declaration is ignored because test already exists
console.log(test); // function
test = "I am variable";
console.log(test); // "I am variable"So the priority is:
- Function declaration hoisting wins over var declaration hoisting.
- But later runtime assignment can still overwrite the function.
Case 2: Multiple function declarations with same name
console.log(foo()); // Output: second
function foo() {
return "first";
}
function foo() {
return "second";
}If multiple function declarations use the same name, the later one overrides the earlier one during hoisting.
Interview answer in one line
If a function declaration and a var declaration have the same name, the function is available first. After that, if the variable gets assigned a new value, that assignment replaces the function reference.
3. Mixed Hoisting Examples
Example 3: Hoisting with closures
function createCounter() {
console.log(count); // Output: undefined (var is hoisted)
var count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // Output: 1
console.log(counter()); // Output: 2In closures, hoisting affects the variable accessibility. The count variable is hoisted and accessible throughout the function, but it's undefined until the assignment line is reached.
Example 5: Hoisting with async functions
async function fetchData() {
console.log(data); // Output: undefined
var data = await fetch('https://api.example.com/data');
return data;
}Async functions follow the same hoisting rules as regular functions. The data variable is hoisted and accessible throughout the function, but it's undefined until the await assignment.
Example 6: Hoisting with classes (not hoisted like functions)
// new MyClass(); // ReferenceError: Cannot access 'MyClass' before initialization
class MyClass {
constructor() {
console.log('Class instantiated');
}
}Unlike function declarations, class declarations are not hoisted. You must declare a class before you can instantiate it. This is one of the key differences between classes and functions in JavaScript.
Example 7: Hoisting with destructuring
console.log(destructuredVar); // Output: undefined
var { destructuredVar } = { destructuredVar: 'Destructured value' };
console.log(destructuredVar); // Output: Destructured valueDestructuring assignments follow the same hoisting rules as regular variable declarations. The variable is hoisted and initialized to undefined until the destructuring assignment is executed.
4. Common Hoisting Scenarios and Solutions
// Scenario 1: Avoiding hoisting issues with proper declaration order
// ❌ Bad practice - relying on hoisting
console.log(userName);
var userName = 'John';
// ✅ Good practice - declare at the top
var userName = 'John';
console.log(userName);
// Scenario 2: Using let/const to prevent hoisting confusion
// ❌ Confusing with var
console.log(age); // undefined
var age = 25;
// ✅ Clear with let/const
// console.log(age); // ReferenceError - forces proper declaration order
let age = 25;
console.log(age);
// Scenario 3: Function declaration vs expression for utilities
// ✅ Good for utilities (fully hoisted)
function formatName(firstName, lastName) {
return firstName + ' ' + lastName;
}
console.log(formatName('John', 'Doe')); // Works anywhere in scope
// ✅ Good for callbacks (not hoisted, but more flexible)
const processData = function(data) {
return data.map(item => item.toUpperCase());
};
// ✅ Good - conditional assignment
let conditionalFunction;
if (userType === 'admin') {
conditionalFunction = function() {
console.log('Admin function');
};
}
// conditionalFunction(); // Only works if assigned5. Advanced Hoisting Examples
// Example 1: Hoisting with try-catch blocks
try {
console.log(errorVar); // Output: undefined
var errorVar = 'This will be hoisted';
throw new Error('Test error');
} catch (error) {
console.log(errorVar); // Output: This will be hoisted
}
// Example 2: Hoisting with switch statements
switch (true) {
case true:
console.log(switchVar); // Output: undefined
var switchVar = 'Switch scoped';
break;
}
console.log(switchVar); // Output: Switch scoped
// Example 3: Hoisting with for-in loops
for (let key in {a: 1, b: 2}) {
console.log(loopVar); // ReferenceError: Cannot access 'loopVar' before initialization
let loopVar = 'Loop scoped';
}
// Example 4: Hoisting with template literals
console.log(templateVar); // Output: undefined
var templateVar = 'Template';
console.log(`${templateVar} literal`); // Output: Template literal
// Example 5: Hoisting with default parameters
function testDefaultParams(param = defaultValue) {
console.log(param);
}
var defaultValue = 'Default value';
testDefaultParams(); // Output: Default value
// Example 6: Hoisting with rest parameters
function testRestParams(...args) {
console.log(restVar); // Output: undefined
var restVar = 'Rest parameter function';
return args;
}
// Example 7: Hoisting with generator functions
function* testGenerator() {
console.log(generatorVar); // Output: undefined
var generatorVar = 'Generator function';
yield 1;
}Key Takeaways:
- `var` declarations are hoisted and initialized to `undefined`.
- `let` and `const` declarations are hoisted but not initialized, creating a temporal dead zone.
- Function declarations are fully hoisted, while function expressions are not.