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
| Declaration | Before its line executes |
|---|---|
var | Exists and is initialized to undefined |
let / const | Exists but cannot be accessed yet |
| Function declaration | Fully initialized and callable |
| Function expression | Follows the rules of its variable declaration |
class | Exists 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.
varis initialized toundefined, function declarations are fully initialized, andlet,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.