JavaScript hoisting

Hoisting describes how JavaScript creates bindings for declarations before executing the code in a scope.

A useful mental model is that declarations are registered during scope creation, but initialization depends on the declaration type. JavaScript does not literally move source code to the top.

Quick comparison

DeclarationBefore its line executes
varExists and is initialized to undefined
let / constExists but cannot be accessed yet
Function declarationFully initialized and callable
Function expressionFollows the rules of its variable declaration
classExists but cannot be accessed yet

var

console.log(name); // undefined
var name = "Ankit";

The declaration is available from the beginning of the function or global scope, but the assignment happens when execution reaches it.

Conceptually:

var name;
console.log(name);
name = "Ankit";

let and const

console.log(name); // ReferenceError
const name = "Ankit";

The binding exists from the beginning of the block, but it remains uninitialized until execution reaches the declaration. This period is called the Temporal Dead Zone (TDZ).

let and const are therefore hoisted, but they cannot be used before initialization.

Functions

A function declaration can be called before its position in the file:

greet(); // Works
 
function greet() {
  console.log("Hello");
}

A function expression follows its variable’s rules:

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

With var, the binding would initially contain undefined, so calling it early would produce a TypeError instead.

Function declaration vs function expression: A declaration is fully hoisted and can be called earlier in the scope; an expression creates a function as a value and follows the rules of its variable. Neither is universally better—prefer declarations for named reusable functions and expressions or arrow functions when passing, composing, or conditionally creating functions. In general, declaring before use keeps control flow easier to follow.

Classes

const user = new User(); // ReferenceError
 
class User {}

Class declarations behave like let and const: their bindings exist early but remain in the Temporal Dead Zone until evaluated.

Interview answer

Hoisting means JavaScript establishes declarations before executing a scope. var is initialized to undefined, function declarations are fully initialized, and let, const, and classes remain inaccessible in the Temporal Dead Zone until execution reaches their declarations. The source code is not physically moved.

Code quiz

Quiz 1 — var

console.log(count);
var count = 5;
console.log(count);

Answer: undefined, then 5.

The binding is initialized to undefined before execution, but the assignment happens at its original line.

Quiz 2 — Temporal Dead Zone

console.log(count);
let count = 5;

Answer: ReferenceError.

count exists, but it remains uninitialized in the Temporal Dead Zone until the declaration executes.

Quiz 3 — Function declaration

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

Answer: Hello.

Function declarations are fully initialized during scope creation.

Quiz 4 — Function expression with var

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

Answer: TypeError: sayHello is not a function.

The var binding exists, but its value is initially undefined.

Quiz 5 — Shadowing and the TDZ

const value = "outer";
 
{
  console.log(value);
  const value = "inner";
}

Answer: ReferenceError.

The inner value shadows the outer one throughout the block. Before its declaration, the inner binding is in the Temporal Dead Zone.

Quiz 6 — Function scope

var value = 1;
 
function test() {
  console.log(value);
  var value = 2;
}
 
test();

Answer: undefined.

The local var value is function-scoped and shadows the outer variable from the beginning of the function.

Quiz 7 — Class declaration

const instance = new User();
 
class User {}

Answer: ReferenceError.

Class declarations remain in the Temporal Dead Zone until evaluated.

Quiz 8 — typeof and var

console.log(typeof value);
var value = 10;

Answer: "undefined".

The var binding already exists and contains undefined.

Quiz 9 — typeof and the TDZ

console.log(typeof value);
let value = 10;

Answer: ReferenceError.

typeof does not bypass the Temporal Dead Zone for an existing lexical binding.

Quiz 10 — Assignment before var

value = 10;
console.log(value);
var value;

Answer: 10.

The var declaration is initialized to undefined before execution, and the first line assigns 10 to it.

Quiz 11 — Assignment before let

value = 10;
let value;

Answer: ReferenceError.

The assignment tries to access value while it is still in the Temporal Dead Zone.

Quiz 12 — Inner function declaration

function outer() {
  inner();
 
  function inner() {
    console.log("inner");
  }
}
 
outer();

Answer: "inner".

The inner function declaration is initialized when the outer function scope is created.

Quiz 13 — Function declaration vs var

console.log(typeof greet);
 
var greet = "hello";
 
function greet() {}

Answer: "function".

During scope creation, the function declaration initializes greet. The later assignment changes it to the string "hello".

Quiz 14 — Closure over a later declaration

function showValue() {
  console.log(value);
}
 
let value = 42;
showValue();

Answer: 42.

The function executes only after value has been initialized. Where a function is declared is less important than when it is called.

Quiz 15 — Call before lexical initialization

function showValue() {
  console.log(value);
}
 
showValue();
let value = 42;

Answer: ReferenceError.

The function runs while value is still in the Temporal Dead Zone.

Quiz 16 — Default parameter scope

let value = "outer";
 
function show(value = value) {
  console.log(value);
}
 
show();

Answer: ReferenceError.

The parameter’s own value shadows the outer variable. Its default expression tries to read that parameter before initialization.

Quiz 17 — var inside a block

if (true) {
  var message = "hello";
}
 
console.log(message);

Answer: "hello".

var is function-scoped or global-scoped, not block-scoped.

Quiz 18 — let inside a block

if (true) {
  let message = "hello";
}
 
console.log(message);

Answer: ReferenceError: message is not defined.

The let binding exists only inside the block.

Quiz 19 — Loop with var

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}

Answer: 3, 3, 3.

All callbacks close over the same function-scoped i, which is 3 when they execute.

Quiz 20 — Loop with let

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}

Answer: 0, 1, 2.

let creates a new binding for each loop iteration.

Knowledge check — Q&A

Q: Does JavaScript physically move declarations to the top of a scope?

No. Hoisting is a mental model for how bindings are created before code execution.

Q: Are let and const hoisted?

Yes. Their bindings are created early, but they cannot be accessed before initialization because of the Temporal Dead Zone.

Q: Why does reading var before its declaration return undefined?

The binding is created and initialized to undefined during scope creation. Its assignment occurs later during execution.

Q: What is the Temporal Dead Zone?

The time between entering a scope and executing a let, const, or class declaration. Accessing the binding during this period throws a ReferenceError.

Q: Are function expressions hoisted like function declarations?

No. A function expression follows the hoisting behavior of the variable that stores it.

Q: What is the difference between an early call to a var function expression and a const function expression?

A var binding contains undefined, so calling it throws a TypeError. A const binding is in the Temporal Dead Zone, so accessing it throws a ReferenceError.

Q: Are classes hoisted?

Yes, but they remain uninitialized in the Temporal Dead Zone until their declaration executes.

Q: What is the practical coding advice?

Declare variables before use, prefer let and const over var, and avoid relying on hoisting for program flow.