DocsHub
Functions

Hoisting

Learn how JavaScript moves variable and function declarations to the top before running your code.

Hoisting

JavaScript does not just read and run your code top to bottom in one pass. Before it runs anything, it does a quick scan of your code and moves certain declarations to the top of their scope. This process is called hoisting.

This is why you can sometimes use things before you define them — and why other times you get a confusing error even though the variable clearly exists.

// You wrote this
console.log(name);
var name = "Ali";

// JavaScript sees this
var name;
console.log(name); // undefined
name = "Ali";

The declaration was hoisted — moved to the top. The assignment stayed where it was.


What Gets Hoisted

Not everything is hoisted the same way. There are three different behaviors depending on what you are declaring.

Hoisting Function Declarations var let and const Can Call Before Declaration Value is undefined Before Assignment ReferenceError in TDZ

1. Function Declarations — Fully Hoisted

Function declarations are hoisted completely — both the name and the entire body. This means you can call a function before you define it.

sayHello(); // ✅ Hello! — works perfectly

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

JavaScript moves the entire function to the top before running anything. So by the time sayHello() is called, the function is already fully defined and ready.

This is why function declarations can be called from anywhere in the file — above or below the definition.

const result = add(10, 5); // ✅ 15 — works fine
console.log(result);

function add(a, b) {
  return a + b;
}

2. var — Hoisted but undefined

Variables declared with var are hoisted to the top of their function scope — but only the declaration, not the value. Until the assignment line runs, the value is undefined.

console.log(age); // undefined — not an error, but not 22 either
var age = 22;
console.log(age); // 22

JavaScript internally rewrites this as:

var age;           // declaration hoisted to top
console.log(age);  // undefined
age = 22;          // assignment stays here
console.log(age);  // 22

This is the dangerous part of var. Using a variable before its assignment gives you undefined silently — no error, no warning. The bug hides itself.

function calculateTotal() {
  console.log(tax);   // undefined — not an error
  var tax = 0.17;
  console.log(tax);   // 0.17
}

calculateTotal();

Inside a function, var is hoisted to the top of that function — not to the global scope.


3. let and const — Temporal Dead Zone

let and const are also hoisted — but they are not initialized. They sit in what is called the Temporal Dead Zone (TDZ) from the top of their scope until the line where they are declared.

Accessing them during this time throws a ReferenceError.

console.log(name); // ❌ ReferenceError: Cannot access 'name' before initialization
let name = "Ali";
console.log(PI); // ❌ ReferenceError: Cannot access 'PI' before initialization
const PI = 3.14;

This is the correct behavior. An error is far better than silent undefined. It tells you exactly what went wrong and where.


The Temporal Dead Zone Visualized

Yes No Enter Scope let name Exists in Memory Temporal Dead Zone Access name? ReferenceError Reach Declaration let name = "Ali" Variable Initialized Use name Normally

The TDZ starts at the top of the scope and ends at the exact line where the variable is declared. The variable exists — JavaScript knows about it — but it is deliberately inaccessible until initialized.


Side by Side — All Three Behaviors

// --- Function declaration ---
greet(); // ✅ "Hello!" — fully hoisted
function greet() {
  console.log("Hello!");
}

// --- var ---
console.log(x); // undefined — hoisted but not assigned yet
var x = 10;
console.log(x); // 10

// --- let ---
console.log(y); // ❌ ReferenceError — in TDZ
let y = 20;
console.log(y); // 20

// --- const ---
console.log(z); // ❌ ReferenceError — in TDZ
const z = 30;
console.log(z); // 30
Hoisted?Initialized?Usable before declaration?
Function declaration✅ Yes✅ Yes — full body✅ Yes
var✅ Yes⚠️ As undefined⚠️ Returns undefined
let✅ Yes❌ TDZ❌ ReferenceError
const✅ Yes❌ TDZ❌ ReferenceError

Function Expressions and Arrow Functions Are Not Hoisted

This is an important distinction. Only function declarations are fully hoisted. Function expressions and arrow functions are stored in variables — so they follow the rules of whatever keyword you used.

// ❌ const — TDZ
greet(); // ReferenceError
const greet = function() {
  console.log("Hello!");
};

// ❌ let — TDZ
sayHi(); // ReferenceError
let sayHi = () => {
  console.log("Hi!");
};

// ❌ var — hoisted but undefined
welcome(); // TypeError: welcome is not a function
var welcome = function() {
  console.log("Welcome!");
};

The last one is tricky. var welcome is hoisted and set to undefined. Then welcome() tries to call undefined as a function — which gives a TypeError, not a ReferenceError.


Why Hoisting Exists

Hoisting was not designed to be a feature you rely on — it is a consequence of how JavaScript's engine works. Before executing code, the engine does a compilation phase where it registers all declarations. Function declarations get fully registered so they are available throughout their scope.

The practical takeaway is simple:

Write code that reads top to bottom — define things before you use them. Only use function declaration hoisting intentionally when it genuinely improves readability. Never rely on var hoisting — it hides bugs.


A Real Example — The Problem With var Hoisting

Here is how var hoisting causes a real bug:

function processScores(scores) {
  for (var i = 0; i < scores.length; i++) {
    var result = scores[i] >= 50 ? "pass" : "fail";
    console.log(`Score ${scores[i]}: ${result}`);
  }

  // var leaks out of the for block
  console.log(`Last i: ${i}`);       // 3 — loop counter leaked
  console.log(`Last result: ${result}`); // "pass" or "fail" — leaked too
}

processScores([72, 45, 88]);
// Score 72: pass
// Score 45: fail
// Score 88: pass
// Last i: 3
// Last result: pass

Both i and result leak outside the loop because var does not respect block scope. With let:

function processScores(scores) {
  for (let i = 0; i < scores.length; i++) {
    let result = scores[i] >= 50 ? "pass" : "fail";
    console.log(`Score ${scores[i]}: ${result}`);
  }

  console.log(i);      // ❌ ReferenceError — stays in the loop
  console.log(result); // ❌ ReferenceError — stays in the loop
}

let keeps variables exactly where they belong. The error tells you immediately that you are trying to access something outside its scope.


Summary

  • Hoisting is JavaScript moving declarations to the top of their scope before running code
  • Function declarations are fully hoisted — you can call them before they are defined
  • var is hoisted but initialized as undefined — using it early gives silent wrong values
  • let and const are hoisted but sit in the Temporal Dead Zone — using them early throws a ReferenceError
  • Function expressions and arrow functions follow the rules of their variable keyword — they are not fully hoisted
  • Always define variables and functions before using them — do not rely on hoisting behavior
  • The TDZ is a feature, not a bug — a clear error is better than silent undefined

On this page