JavaScript Hoisting
Hoisting is JavaScript's behavior of moving declarations to the top of their scope during compilation. Understanding hoisting helps you avoid bugs and write more predictable code.
Try It Yourself
Experiment with different types of hoisting to see how JavaScript handles declarations:
// Hoisting in action - try modifying this code!
// 1. Function hoisting - works!
sayHello(); // Can call before declaration
function sayHello() {
console.log("Hello from a hoisted function!");
}
// 2. var hoisting - returns undefined
console.log("myVar before declaration:", myVar);
var myVar = "I'm a var";
console.log("myVar after declaration:", myVar);
// 3. let/const - Temporal Dead Zone
// Uncomment below to see ReferenceError:
// console.log(myLet); // ReferenceError!
let myLet = "I'm a let";
console.log("myLet after declaration:", myLet);
// 4. Function expression - NOT fully hoisted
// Uncomment to see error:
// greet(); // TypeError: greet is not a function
var greet = function() {
console.log("Hello from function expression!");
};
greet(); // Works after assignmentWhat is Hoisting?
Hoisting is JavaScript's default behavior of moving all declarations to the top of the current scope (script or function) before executing the code.
This means you can use variables and functions before they are declared. However, only declarations are hoisted, not initializations.
var Hoisting
Variables declared with var are hoisted to the top of their function scope and initialized with undefined:
// What you write:
console.log(message); // undefined (not an error!)
var message = "Hello";
console.log(message); // "Hello"
// How JavaScript interprets it:
var message; // Declaration hoisted to top
console.log(message); // undefined
message = "Hello"; // Assignment stays in place
console.log(message); // "Hello"let and const Hoisting
Variables declared with let and const are also hoisted, but they are not initialized. They exist in a "Temporal Dead Zone" until the declaration is reached:
// let and const are hoisted but NOT initialized
// They exist in the "Temporal Dead Zone" (TDZ)
console.log(name); // ReferenceError: Cannot access 'name' before initialization
let name = "Alice";
// Same with const
console.log(PI); // ReferenceError
const PI = 3.14159;The Temporal Dead Zone (TDZ)
The TDZ is the time between entering a scope and the variable being declared. Accessing the variable during this period throws a ReferenceError:
// The Temporal Dead Zone (TDZ) visualized
{
// TDZ starts here for 'count'
// -------------------------
// Any access to 'count' here throws ReferenceError
console.log(typeof undeclaredVar); // "undefined" - no error for undeclared
// console.log(typeof count); // ReferenceError! TDZ prevents this
// TDZ ends here
// -------------------------
let count = 10;
console.log(count); // 10 - works fine after declaration
}Hoisting Comparison
Here's how different declaration types behave with hoisting:
| Feature | var | let | const | function |
|---|---|---|---|---|
| Hoisted | ||||
| Initialized | undefined | Full body | ||
| TDZ applies | ||||
| Can use before declaration | Yes (undefined) | |||
| Scope | Function | Block | Block | Function* |
* Function declarations are hoisted to function scope, but in blocks they may vary by environment.
Function Hoisting
Function Declarations
Function declarations are fully hoisted, including their body. You can call them before they appear in the code:
// Function declarations are FULLY hoisted
// You can call them before they appear in code
greet("World"); // "Hello, World!" - works!
function greet(name) {
console.log("Hello, " + name + "!");
}
greet("JavaScript"); // "Hello, JavaScript!"Function Expressions
Function expressions (including arrow functions) are not fully hoisted. Only the variable declaration is hoisted:
// Function expressions are NOT fully hoisted
// Only the variable declaration is hoisted
// sayHi(); // TypeError: sayHi is not a function
var sayHi = function() {
console.log("Hi!");
};
sayHi(); // Works after the assignment
// Arrow functions behave the same way
// greet(); // TypeError: greet is not a function
var greet = () => {
console.log("Hello!");
};
greet(); // Works after the assignmentClass Hoisting
Classes behave like let and const. They are hoisted but not initialized, so the TDZ applies:
// Classes are NOT hoisted like functions
// They behave like let/const (TDZ applies)
// const dog = new Animal("Dog"); // ReferenceError!
class Animal {
constructor(name) {
this.name = name;
}
}
const cat = new Animal("Cat"); // Works after declaration
console.log(cat.name); // "Cat"Hoisting Priority
When a variable and function have the same name, function declarations take precedence during hoisting:
// When both variable and function have the same name,
// function declarations take precedence
console.log(typeof foo); // "function" (not undefined)
var foo = "I'm a variable";
function foo() {
return "I'm a function";
}
console.log(typeof foo); // "string" (reassigned to variable value)Common Hoisting Mistakes
Loop Variables with var
Using var in loops with asynchronous callbacks is a classic hoisting-related bug:
// Classic mistake: var in loops with async callbacks
// Problem with var
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log("var i:", i);
}, 100);
}
// Output: 3, 3, 3 (not 0, 1, 2!)
// Because var is hoisted and shared across iterations
// Solution: use let
for (let j = 0; j < 3; j++) {
setTimeout(function() {
console.log("let j:", j);
}, 200);
}
// Output: 0, 1, 2 (correct!)
// Because let creates a new binding per iterationBest Practices
// Best practices to avoid hoisting confusion
// 1. Always declare variables at the top of their scope
function goodExample() {
// Declarations at the top
let name;
let age;
const MAX_AGE = 120;
// Logic follows
name = "Alice";
age = 30;
console.log(name, age);
}
// 2. Use const by default, let when needed
const API_URL = "https://api.example.com";
let userCount = 0;
userCount++; // Can reassign let
// 3. Declare functions before calling them
function helper() {
return "I help!";
}
console.log(helper()); // Clear order of executionSummary of Best Practices
- Use const by default, let when you need to reassign
- Avoid var in modern JavaScript
- Declare variables at the top of their scope
- Define functions before using them for clarity
- Never rely on hoisting - write code as if it doesn't exist
Test Your Knowledge
JavaScript Hoisting Quiz
5 questionsWhat will this code output? console.log(x); var x = 5;
What will this code output? console.log(y); let y = 10;
Can you call a function declaration before it appears in the code?
What is the Temporal Dead Zone?
What will this code output? greet(); var greet = function() { console.log("Hi"); };
Coding Challenge
Look at the code and predict what each console.log will output. Then run it to check your answers. Understanding hoisting means you can predict JavaScript's behavior!
// Challenge: Predict the output
// Before running, write down what you think each console.log will output
console.log("1:", typeof myFunc);
console.log("2:", typeof myVar);
console.log("3:", myVar);
var myVar = "Hello";
function myFunc() {
return "World";
}
console.log("4:", typeof myFunc);
console.log("5:", myVar);
console.log("6:", myFunc());
// Write your predictions here as comments:
// 1: ???
// 2: ???
// 3: ???
// 4: ???
// 5: ???
// 6: ???Summary
- Hoisting moves declarations to the top of their scope during compilation
- var is hoisted and initialized to
undefined - let and const are hoisted but remain in the Temporal Dead Zone until declared
- Function declarations are fully hoisted (including body)
- Function expressions only have the variable hoisted, not the function
- Classes behave like let/const with TDZ
- Best practice: Use const/let, declare at the top, don't rely on hoisting