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:

index.js
// 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 assignment

What 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.

Hoisting is Conceptual
JavaScript doesn't actually move your code. During the compile phase, it first processes all declarations, then executes the code. This creates the effect of "hoisting."

var Hoisting

Variables declared with var are hoisted to the top of their function scope and initialized with undefined:

javascript
// 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"
Unexpected undefined
This behavior can lead to subtle bugs. You might expect an error when accessing an undeclared variable, but with var you get undefined instead.

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:

javascript
// 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:

javascript
// 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
}
TDZ is a Feature, Not a Bug
The TDZ helps catch programming errors by preventing you from accidentally using variables before they're properly initialized.

Hoisting Comparison

Here's how different declaration types behave with hoisting:

Featurevarletconstfunction
Hoisted
InitializedundefinedFull body
TDZ applies
Can use before declarationYes (undefined)
ScopeFunctionBlockBlockFunction*

* 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:

javascript
// 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:

javascript
// 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 assignment

Class Hoisting

Classes behave like let and const. They are hoisted but not initialized, so the TDZ applies:

javascript
// 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:

javascript
// 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)
Avoid Name Conflicts
Never use the same name for variables and functions. It creates confusing code that's hard to debug.

Common Hoisting Mistakes

Loop Variables with var

Using var in loops with asynchronous callbacks is a classic hoisting-related bug:

javascript
// 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 iteration

Best Practices

javascript
// 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 execution

Summary 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 questions
Question 1

What will this code output? console.log(x); var x = 5;

Question 2

What will this code output? console.log(y); let y = 10;

Question 3

Can you call a function declaration before it appears in the code?

Question 4

What is the Temporal Dead Zone?

Question 5

What will this code output? greet(); var greet = function() { console.log("Hi"); };

Coding Challenge

Predict the Hoisting Output
Medium

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!

Starter Code
// 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