Hoisting is an important JavaScript concept that explains how variable and function declarations behave during code execution. It helps you understand why some functions can be called before their declarations appear in the source code, while accessing certain variables too early causes an error.
JavaScript handles declarations differently depending on whether you use var, let, const, or a function declaration. Understanding these differences will help you avoid common errors and write more predictable JavaScript programs.
In this tutorial, you will learn how hoisting works, how variable and function hoisting differ, why let and const behave differently from var, and what the Temporal Dead Zone means.
What Is Hoisting in JavaScript?
Hoisting describes how JavaScript processes declarations before executing the statements in a scope.
JavaScript creates the necessary bindings for declarations during the setup of an execution context. The behavior depends on the kind of declaration being processed.
For example, consider this code:
console.log(userName);
var userName = "Karim";
Output:
undefined
This happens because the var declaration is processed before the code executes, but its assignment remains at the original location.
Conceptually, the code behaves like this:
var userName;
console.log(userName);
userName = "Karim";
JavaScript does not literally move the source code. Hoisting is a way to describe the language’s declaration and initialization behavior.
What Is Variable Hoisting in JavaScript?
Variable hoisting refers to how JavaScript processes variable declarations made with var, let, and const.
Although all three declarations are established as part of scope setup, they do not behave identically when accessed before their declaration.
vardeclarations are initialized withundefinedwhen their scope is set up.letdeclarations exist but cannot be accessed before initialization.constdeclarations also exist but cannot be accessed before initialization and must be initialized when declared.
Variable Hoisting With var
Consider this example:
console.log(age);
var age = 22;
console.log(age);
Output:
undefined
22
The first console.log() prints undefined because the var binding has already been initialized, but the assignment to 22 has not executed yet.
The second console.log() prints 22 because the assignment has occurred.
Variable Hoisting With let
Now consider the same example using let:
console.log(age);
let age = 22;
This code throws a ReferenceError.
The let declaration is known to the scope, but the variable cannot be accessed before its initialization.
Variable Hoisting With const
The same rule applies to const.
console.log(country);
const country = "India";
This code also throws a ReferenceError.
The difference is not that let and const are completely ignored during hoisting. Their bindings are established, but they remain uninitialized until execution reaches their declarations.
What Is var Hoisting?
The var keyword declares a function-scoped or globally scoped variable, depending on where it is used.
A var declaration inside a function belongs to that function’s scope. A top-level var declaration in a classic browser script has global-script behavior and creates a property on window.
Accessing a var Variable Before Declaration
console.log(message);
var message = "Hello JavaScript!";
console.log(message);
Output:
undefined
Hello JavaScript!
The first statement does not throw an error because the var binding has been initialized to undefined.
var Hoisting Inside a Function
function showMessage() {
console.log(message);
var message = "Welcome!";
console.log(message);
}
showMessage();
Output:
undefined
Welcome!
The message variable is function-scoped. Its declaration is processed when the function’s execution context is established, while its assignment occurs when execution reaches that line.
Conceptually, the function behaves like this:
function showMessage() {
var message;
console.log(message);
message = "Welcome!";
console.log(message);
}
var Hoisting Does Not Remove Scope Rules
A variable declared with var inside a function is not available outside that function.
function calculate() {
var total = 100;
}
calculate();
console.log(total);
This throws a ReferenceError because total belongs to the function scope.
Hoisting affects declaration behavior, but it does not make every variable globally accessible.
What Is Function Hoisting in JavaScript?
Function hoisting describes how JavaScript processes function declarations.
A traditional function declaration can generally be called before the line where it appears because the function is initialized during scope setup.
Calling a Function Before Its Declaration
greet();
function greet() {
console.log("Hello from JavaScript!");
}
Output:
Hello from JavaScript!
The function declaration is available when the execution reaches the call to greet().
This behavior differs from a function expression assigned to a variable.
Function Declarations vs Function Expressions
A function declaration uses this syntax:
function greet() {
console.log("Hello!");
}
greet();
A function expression assigns a function to a variable:
const greet = function () {
console.log("Hello!");
};
greet();
With a function expression, the variable follows the hoisting rules of its declaration.
For example:
greet();
var greet = function () {
console.log("Hello!");
};
This throws a TypeError because greet is initially undefined, and JavaScript cannot call undefined as a function.
The declaration of greet is processed before execution, but the function expression is not assigned to it until the assignment statement runs.
Arrow Functions and Hoisting
Arrow functions follow the hoisting rules of the variable used to store them.
For example:
sayHello();
const sayHello = () => {
console.log("Hello!");
};
This throws a ReferenceError because sayHello is accessed before the const declaration has been initialized.
If you use var instead, the error is different:
sayHello();
var sayHello = () => {
console.log("Hello!");
};
This throws a TypeError because sayHello is initially undefined.
For more examples, see JavaScript Arrow Functions.
What Happens With let and const During Hoisting?
The let and const keywords introduce block-scoped declarations. Their bindings are established when the relevant scope is initialized, but they cannot be accessed before initialization.
This is why code that works with var may throw an error when rewritten with let or const.
Example Using let
{
console.log(score);
let score = 90;
}
This throws a ReferenceError.
The variable score is known to the block, but it has not been initialized when console.log(score) executes.
Example Using const
{
console.log(siteName);
const siteName = "Web4Learners";
}
This also throws a ReferenceError.
The const declaration is subject to the same pre-initialization access restriction.
Why const Must Be Initialized
Unlike let, a const declaration must include an initializer.
Incorrect:
const age;
age = 22;
This produces a SyntaxError because a const declaration without an initializer is not allowed in this context.
Correct:
const age = 22;
console.log(age);
Output:
22
Remember that const prevents reassignment of the binding. It does not make an object or array immutable.
What Is the Temporal Dead Zone in JavaScript?
The Temporal Dead Zone (TDZ) is the period between entering a scope and the point where a let or const variable is initialized.
During this period, accessing the variable causes a ReferenceError.
The TDZ explains why let and const declarations behave differently from var declarations.
Example of the Temporal Dead Zone
{
// Temporal Dead Zone starts for "name"
console.log(name);
let name = "Karim";
// Temporal Dead Zone ends after initialization
console.log(name);
}
The first console.log(name) throws a ReferenceError, so the program does not reach the second log in this example.
To see the successful output, run the corrected version:
{
let name = "Karim";
console.log(name);
}
Output:
Karim
The variable becomes accessible only after its declaration has been evaluated and initialization has occurred.
TDZ With const
{
console.log(price);
const price = 500;
}
This throws a ReferenceError because price is still in its Temporal Dead Zone when it is accessed.
TDZ Also Applies to Function Parameters
Default parameter initializers can also encounter TDZ-related errors when they refer to a later parameter.
function example(a = b, b = 10) {
return [a, b];
}
example();
This throws a ReferenceError because b has not yet been initialized when the default value for a is evaluated.
The reverse order works:
function example(a = 10, b = a) {
return [a, b];
}
console.log(example());
Output:
[10, 10]
The default for a is evaluated first, so b can safely use its value.
Variable Hoisting vs Function Hoisting
Variable declarations and function declarations have different initialization behavior.
| Declaration type | Accessible before declaration? | Behavior |
|---|---|---|
var variable | Yes | Reads as undefined before assignment |
let variable | No | Throws a ReferenceError before initialization |
const variable | No | Throws a ReferenceError before initialization |
| Function declaration | Generally yes | Function is initialized during scope setup |
Function expression with var | Variable exists, but is initially undefined | Calling it before assignment throws a TypeError |
Arrow function with const | No | Access before initialization throws a ReferenceError |
These differences are important when reading code, debugging errors, and deciding where to place declarations.
Common Hoisting Mistakes in JavaScript
Assuming Every Declaration Can Be Used Before Its Line
Not all declarations behave like traditional function declarations. let, const, and function expressions follow different rules.
Declare variables before using them to make your code easier to read.
Confusing undefined With a ReferenceError
A var variable can exist with the value undefined before its assignment runs.
A let or const variable accessed before initialization throws a ReferenceError.
These are different behaviors and should not be treated as interchangeable.
Assuming Arrow Functions Are Hoisted Like Function Declarations
Arrow functions are expressions, so they follow the rules of the variable used to store them.
Believing Hoisting Literally Moves Code
Hoisting is a useful way to describe JavaScript’s declaration processing. The JavaScript engine does not simply rearrange your source code and execute the rearranged version.
Frequently Asked Questions
What is hoisting in JavaScript?
Hoisting describes how JavaScript establishes and initializes declarations during scope setup before executing statements. The behavior depends on the declaration type.
Are let and const hoisted?
Yes. Their bindings are established when the scope is initialized, but they cannot be accessed before their declarations initialize them. Accessing them during this period throws a ReferenceError.
Why does var return undefined before assignment?
A var binding is initialized to undefined during scope setup. The assignment happens later when execution reaches the assignment statement.
Are function declarations hoisted?
Traditional function declarations are initialized during scope setup, so they can generally be called before their declaration appears in the code.
Are arrow functions hoisted?
Arrow functions are not initialized like function declarations. Their availability depends on the variable declaration that stores them. For example, accessing a const arrow function before initialization throws a ReferenceError.
What is the Temporal Dead Zone?
The Temporal Dead Zone is the period during which a let or const binding exists but cannot yet be accessed because it has not been initialized.
How can I avoid hoisting-related errors?
Declare variables before using them, prefer const when reassignment is unnecessary, use let when reassignment is needed, and distinguish function declarations from function expressions.
Conclusion
Hoisting explains how JavaScript processes declarations before executing statements. Traditional function declarations can be called before their source-code position, while var variables are initialized to undefined until their assignments run.
The let and const keywords behave differently because their bindings cannot be accessed before initialization. This restriction creates the Temporal Dead Zone.
Understanding variable hoisting, function hoisting, and the TDZ will help you recognize common JavaScript errors and write clearer, more predictable programs.