JavaScript Hoisting
Learn how JavaScript hoisting works with variables, functions, let, const, var, function declarations, function expressions, arrow functions, classes, and the Temporal Dead Zone.
Introduction
JavaScript sometimes allows variables and functions to be referenced before the line where they are declared. This behavior is commonly known as hoisting.
Hoisting is one of the most misunderstood concepts in JavaScript. Many beginners imagine that JavaScript physically moves declarations to the top of the file, but that is not what actually happens.
Before executing code, JavaScript creates bindings for variables, functions, classes, and other declarations. Different declaration types are initialized differently, which creates the behavior developers call hoisting.
Understanding hoisting helps you predict program output, understand ReferenceError and TypeError messages, work with the Temporal Dead Zone, and avoid bugs caused by accessing declarations too early.
- What hoisting means in JavaScript.
- Why declarations appear to move upward.
- How JavaScript prepares code before execution.
- What happens during the creation phase.
- What happens during the execution phase.
- How var is hoisted.
- Why var contains undefined before assignment.
- The difference between declaration and assignment.
- How let is hoisted.
- How const is hoisted.
- What the Temporal Dead Zone is.
- How the TDZ works inside blocks.
- Why typeof can fail inside the TDZ.
- How function declarations are hoisted.
- Why function declarations can be called early.
- How function expressions are hoisted.
- How arrow functions are hoisted.
- How named function expressions behave.
- How classes are hoisted.
- How imports behave before normal code execution.
- How hoisting interacts with scope.
- How hoisting works inside functions.
- How hoisting works inside blocks.
- How declaration name conflicts are resolved.
- How to debug hoisting problems.
- How to write predictable code that avoids hoisting confusion.
What is Hoisting?
Hoisting is the behavior produced when JavaScript creates declarations and bindings before executing the code in their scope.
console.log(message);
var message = 'Hello JavaScript';The program does not produce a ReferenceError because the var declaration is processed before normal execution begins. However, the assignment happens later, so the value is undefined when the first console.log runs.
var message;
console.log(message);
message = 'Hello JavaScript';- JavaScript does not physically rewrite your source code.
- Declarations are not literally moved to the top.
- The JavaScript engine prepares bindings before execution.
- Different declaration types are prepared differently.
- Hoisting is the visible result of this preparation process.
Why Do We Need to Understand Hoisting?
Hoisting affects the behavior of variables, functions, classes, modules, and many common JavaScript patterns. Without understanding it, some outputs and errors can appear unexpected.
Predict Output
Understand why some values are undefined before their assignment.
Debug Errors
Identify ReferenceError and TypeError problems caused by early access.
Understand Execution
Learn how JavaScript prepares a scope before running statements.
Understand Functions
Know why function declarations and function expressions behave differently.
Understand the TDZ
Learn why let, const, and class declarations cannot be accessed too early.
Write Cleaner Code
Structure declarations to make execution easier to understand.
Real-World Analogy
Imagine a hotel preparing rooms before guests arrive. The hotel first creates a list of all reservations. However, not every room is immediately ready for use.
BEFORE GUESTS ARRIVE
Hotel checks all reservations
│
▼
┌───────────────────────────────┐
│ Reservation: var Guest │
│ Room exists │
│ Initial status: undefined │
├───────────────────────────────┤
│ Reservation: let Guest │
│ Room exists │
│ Not accessible yet │
├───────────────────────────────┤
│ Reservation: const Guest │
│ Room exists │
│ Not accessible yet │
├───────────────────────────────┤
│ Function Reservation │
│ Room fully prepared │
│ Ready immediately │
└───────────────────────────────┘
THEN NORMAL EXECUTION BEGINSThe reservations already exist before normal activity begins, but their availability differs. This is similar to how JavaScript prepares declarations before executing statements.
How JavaScript Executes Code
A simplified way to understand JavaScript execution is to divide it into two major stages: the creation phase and the execution phase.
JAVASCRIPT CODE
│
▼
┌─────────────────────────┐
│ CREATION PHASE │
│ │
│ Create bindings │
│ Register declarations │
│ Prepare functions │
│ Prepare scope │
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ EXECUTION PHASE │
│ │
│ Run statements │
│ Perform assignments │
│ Call functions │
│ Produce output │
└─────────────────────────┘Creation Phase
Before normal statements execute, JavaScript prepares the declarations in the current scope.
- var bindings are created and initialized with undefined.
- let bindings are created but remain uninitialized.
- const bindings are created but remain uninitialized.
- Function declarations are created with their complete function value.
- Class declarations are created but remain uninitialized.
- The scope environment is prepared.
- Normal assignment expressions have not executed yet.
console.log(score);
showMessage();
var score = 100;
function showMessage() {
console.log('Hello!');
}CREATION PHASE
score
created
value = undefined
showMessage
created
value = complete function
No console.log executed yet
No score = 100 assignment yetExecution Phase
During the execution phase, JavaScript runs statements from top to bottom and performs assignments and function calls.
START EXECUTION
│
▼
console.log(score)
│
▼
Print undefined
│
▼
showMessage()
│
▼
Print Hello!
│
▼
score = 100
│
▼
ENDWhat Actually Gets Hoisted?
Declaration Type Hoisted? Initial Access
-------------------------------------------------------
var Yes undefined
let Yes ReferenceError
const Yes ReferenceError
Function Declaration Yes Full Function
Function Expression Variable Depends on Variable
Arrow Function Variable Depends on Variable
Class Declaration Yes ReferenceError
import Declaration Yes Available Before
Module Body Runs- Do not only ask whether something is hoisted.
- Ask how the declaration is initialized.
- Ask whether it can be accessed before its declaration line.
- Ask what value exists before normal execution reaches the declaration.
Variable Hoisting
Variables declared with var, let, and const all participate in the declaration setup process, but they behave differently before execution reaches their declaration.
console.log(first);
// undefined
console.log(second);
// ReferenceError
console.log(third);
// ReferenceError
var first = 10;
let second = 20;
const third = 30;var Hoisting
A variable declared with var is created and initialized with undefined before the execution phase begins.
console.log(name);
var name = 'Aarav';
console.log(name);The declaration exists before the first console.log, but the assignment to Aarav happens only when execution reaches that line.
How var Hoisting Works
console.log(price);
var price = 1500;
console.log(price);CREATION PHASE
price = undefined
EXECUTION PHASE
Step 1
console.log(price)
Output: undefined
Step 2
price = 1500
Step 3
console.log(price)
Output: 1500var Declaration vs Assignment
The declaration and assignment are two different operations. The declaration is prepared before execution, but the assignment runs at its original position.
var course = 'JavaScript';DECLARATION
var course;
ASSIGNMENT
course = 'JavaScript';- The var declaration is prepared early.
- The assignment is not performed early.
- Before assignment, the value is undefined.
- This is why var can produce unexpected results.
Multiple var Declarations
var allows the same variable to be declared multiple times in the same scope.
console.log(value);
var value = 10;
var value = 20;
console.log(value);The repeated declaration does not create a new binding. The later assignment changes the existing variable value.
var Inside Functions
var is hoisted to the top of its function scope, not necessarily to the top of the entire program.
function showScore() {
console.log(score);
var score = 100;
console.log(score);
}
showScore();function showScore() {
var score = 100;
}
showScore();
console.log(score);let Hoisting
Variables declared with let are hoisted in the sense that their bindings are created before execution, but they are not initialized until execution reaches the declaration.
console.log(score);
let score = 100;- var is initialized with undefined before execution.
- let remains uninitialized before its declaration line.
- Accessing let too early causes a ReferenceError.
- The inaccessible period is called the Temporal Dead Zone.
const Hoisting
const behaves similarly to let before its declaration. Its binding exists, but it cannot be accessed before initialization.
console.log(PI);
const PI = 3.14159;A const declaration must also receive a value when it is declared.
const total;Temporal Dead Zone
The Temporal Dead Zone, commonly called the TDZ, is the period between entering a scope and reaching the declaration of a let, const, or class binding.
// TDZ starts for userName
console.log(userName);
// TDZ ends here
let userName = 'Priya';
console.log(userName);ENTER SCOPE
│
▼
┌─────────────────────────┐
│ TEMPORAL DEAD ZONE │
│ │
│ userName exists │
│ but cannot be accessed │
│ │
│ Access = ReferenceError │
└────────────┬────────────┘
│
▼
let userName = 'Priya'
│
▼
TDZ ENDS
│
▼
userName is accessibleHow the TDZ Works
The TDZ begins when execution enters the variable’s scope. It ends when execution reaches the declaration and initialization.
{
// TDZ begins
console.log(status);
// TDZ ends
let status = 'Active';
console.log(status);
}TDZ with let
function showPrice() {
console.log(price);
let price = 1500;
console.log(price);
}
showPrice();TDZ with const
function showTax() {
console.log(TAX_RATE);
const TAX_RATE = 0.18;
}
showTax();TDZ Inside Blocks
The TDZ applies from the beginning of the block where the variable is declared, even if an outer variable with the same name exists.
const message = 'Global Message';
{
console.log(message);
const message = 'Block Message';
}The inner message declaration shadows the outer message for the entire block. Before the inner declaration line, the inner variable is in the TDZ.
typeof and the Temporal Dead Zone
The typeof operator normally returns undefined for an identifier that does not exist, but it throws a ReferenceError when the identifier exists in the current scope and is still in the TDZ.
console.log(typeof unknownVariable);console.log(typeof score);
let score = 100;var vs let vs const Hoisting
Feature var let const
---------------------------------------------------
Binding Created Yes Yes Yes
Initialized Early Yes No No
Initial Value undefined None None
TDZ No Yes Yes
Access Before
Declaration undefined Error Error
Block Scoped No Yes Yes
Function Scoped Yes Yes Yes
Reassignment Yes Yes No
Recommended Rarely Yes YesFunction Hoisting
Functions behave differently depending on how they are created. Function declarations receive full function values during the creation phase, while function expressions depend on the variable used to store them.
Function Declaration Hoisting
A function declaration is fully available before execution reaches its declaration line.
greet();
function greet() {
console.log('Hello, JavaScript!');
}CREATION PHASE
greet =
complete function
EXECUTION PHASE
greet()
│
▼
Function already available
│
▼
Hello, JavaScript!Calling Functions Before Declaration
Function declaration hoisting allows code to call a function before its declaration appears in the source file.
const result = add(10, 20);
console.log(result);
function add(first, second) {
return first + second;
}- Calling declarations before definition is valid.
- It can allow high-level code to appear before implementation details.
- Some teams still prefer declarations before usage for readability.
- Consistency is more important than relying heavily on hoisting.
Function Expression Hoisting
A function expression is not hoisted as a complete callable function. The variable declaration follows the hoisting rules of var, let, or const.
const greet = function () {
console.log('Hello!');
};var Function Expressions
When a function expression is stored in var, the variable is initialized with undefined before execution.
greet();
var greet = function () {
console.log('Hello!');
};The variable greet exists, but its value is undefined when the call occurs.
CREATION PHASE
greet = undefined
EXECUTION PHASE
greet()
│
▼
undefined()
│
▼
TypeErrorlet Function Expressions
greet();
let greet = function () {
console.log('Hello!');
};const Function Expressions
greet();
const greet = function () {
console.log('Hello!');
};Arrow Function Hoisting
Arrow functions are expressions. Their behavior before declaration depends on the variable used to store them.
calculate();
const calculate = () => {
console.log('Calculating...');
};calculate();
var calculate = () => {
console.log('Calculating...');
};- Arrow functions are not function declarations.
- They are values assigned to variables.
- const arrow functions are inaccessible before initialization.
- let arrow functions are inaccessible before initialization.
- var arrow functions contain undefined before assignment.
Named Function Expressions
A named function expression has an internal function name, but the outer variable still follows normal variable hoisting rules.
const factorial = function calculate(number) {
if (number <= 1) {
return 1;
}
return number * calculate(number - 1);
};
console.log(factorial(5));The name calculate is available inside the function body. The outer variable factorial follows const rules.
Function Declaration vs Function Expression
Function Type Before Declaration
--------------------------------------------------
Function Declaration Callable
var Function Expression TypeError
let Function Expression ReferenceError
const Function Expression ReferenceError
var Arrow Function TypeError
let Arrow Function ReferenceError
const Arrow Function ReferenceErrorFunction and Variable Name Conflicts
When function declarations and var declarations use the same name, the creation process can produce behavior that appears surprising.
console.log(typeof processData);
var processData = 100;
function processData() {
return 'Processing';
}
console.log(typeof processData);During creation, the function declaration provides the initial function value. During execution, the assignment processData = 100 replaces it with a number.
Hoisting Priority
Function declarations are initialized with complete function values, while var declarations begin as undefined. A var declaration does not replace an already initialized function value during the creation phase, although a later assignment can replace it during execution.
console.log(typeof test);
var test;
function test() {
return 'Hello';
}
console.log(typeof test);Hoisting Inside Functions
Every function call creates its own execution context and prepares declarations inside that function.
function calculate() {
console.log(total);
var total = 500;
console.log(total);
}
calculate();CALL calculate()
│
▼
CREATE FUNCTION CONTEXT
│
▼
total = undefined
│
▼
EXECUTE FUNCTION BODY
│
├── Print undefined
│
├── total = 500
│
└── Print 500Hoisting Inside Blocks
let and const declarations belong to their block and enter the TDZ from the beginning of that block.
let status = 'Global';
{
console.log(status);
let status = 'Block';
}Block-Level Function Declarations
In modern strict-mode JavaScript, a function declaration inside a block is scoped to that block.
'use strict';
if (true) {
function showMessage() {
console.log('Inside Block');
}
showMessage();
}
showMessage();- Older environments historically handled block functions inconsistently.
- Modern strict mode provides clearer block behavior.
- JavaScript modules use strict mode automatically.
- For maximum clarity, consider assigning a function expression to const inside a block.
Class Hoisting
Class declarations are hoisted in the sense that their bindings exist before the declaration line, but they remain uninitialized and are subject to the Temporal Dead Zone.
Class Declaration Hoisting
const user = new User('Aarav');
class User {
constructor(name) {
this.name = name;
}
}class User {
constructor(name) {
this.name = name;
}
}
const user = new User('Aarav');
console.log(user.name);Class Expression Hoisting
A class expression follows the hoisting rules of the variable that stores it.
const user = new User('Priya');
const User = class {
constructor(name) {
this.name = name;
}
};Import Hoisting
Static import declarations are processed before the module body executes. Imported bindings are available throughout the module according to module initialization rules.
console.log(calculateTotal(100, 200));
import {
calculateTotal
} from './math.js';export function calculateTotal(
first,
second
) {
return first + second;
}- Static imports are processed before module execution.
- Import declarations can only appear at the module top level.
- Imported bindings are live bindings.
- Import behavior is different from normal variable assignment.
Hoisting and Scope
Declarations are prepared within their own scope. Hoisting does not move a variable outside the scope where it belongs.
function first() {
console.log(value);
var value = 10;
}
function second() {
console.log(value);
var value = 20;
}
first();
second();Each function has its own value binding. The variables are hoisted only within their respective function scopes.
Hoisting and Lexical Scope
Hoisting does not change lexical scope. A function still resolves variables based on where it was defined.
const value = 'Global';
showValue();
function showValue() {
console.log(value);
}Hoisting and Closures
Closures remember lexical bindings. Hoisting determines when those bindings are created, while closures determine how functions continue accessing them.
function createCounter() {
return increase;
let count = 0;
function increase() {
count++;
return count;
}
}
const counter = createCounter();
console.log(counter());The function declaration increase is available early, but createCounter returns before execution initializes count. Calling the returned function later attempts to access an uninitialized binding.
function createCounter() {
let count = 0;
return increase;
function increase() {
count++;
return count;
}
}
const counter = createCounter();
console.log(counter());
console.log(counter());Hoisting in Loops
Loop declarations follow their normal scope and hoisting rules. var creates one function-scoped binding, while let creates iteration-specific bindings.
console.log(i);
for (var i = 0; i < 3; i++) {
console.log(i);
}
console.log(i);console.log(i);
for (let i = 0; i < 3; i++) {
console.log(i);
}Hoisting with Event Handlers
Function declarations can be registered as event handlers before their declaration appears in the source code.
<button id="showButton">
Show Message
</button>
<p id="output"></p>const button =
document.getElementById('showButton');
button.addEventListener(
'click',
showMessage
);
function showMessage() {
const output =
document.getElementById('output');
output.textContent =
'JavaScript function hoisting works!';
}Hoisting with setTimeout
setTimeout(showMessage, 1000);
function showMessage() {
console.log('Message after 1 second');
}setTimeout(showMessage, 1000);
const showMessage = function () {
console.log('Hello');
};Common Hoisting Errors
ERROR TYPE
│
├── ReferenceError
│ let before declaration
│ const before declaration
│ class before declaration
│ undeclared variable
│
└── TypeError
calling undefined
var function expression
before assignmentconsole.log(total);
let total = 100;calculate();
var calculate = function () {
return 100;
};Debugging Hoisting Problems
- Find where the variable or function is declared.
- Identify whether it uses var, let, const, class, or function declaration syntax.
- Check whether access happens before initialization.
- Check the exact scope containing the declaration.
- Look for variable shadowing.
- Look for a Temporal Dead Zone.
- Check whether a function expression is being called too early.
- Check whether var currently contains undefined.
- Read whether the error is ReferenceError or TypeError.
- Move declarations before usage when the flow is unclear.
function calculateOrder() {
console.log(total);
const price = 1000;
const tax = 180;
let total = price + tax;
return total;
}
calculateOrder();function calculateOrder() {
const price = 1000;
const tax = 180;
const total = price + tax;
console.log(total);
return total;
}
calculateOrder();Complete Hoisting Example
The following example demonstrates function declaration hoisting, variable initialization, block scope, and predictable declaration order.
<div class="order-app">
<h2>Order Calculator</h2>
<input
id="priceInput"
type="number"
placeholder="Enter price"
>
<button id="calculateButton">
Calculate Total
</button>
<p id="result">
Total: ₹0
</p>
</div>initializeApplication();
function initializeApplication() {
const priceInput =
document.getElementById('priceInput');
const calculateButton =
document.getElementById(
'calculateButton'
);
const result =
document.getElementById('result');
calculateButton.addEventListener(
'click',
calculateOrder
);
function calculateOrder() {
const price =
Number(priceInput.value);
if (
Number.isNaN(price) ||
price < 0
) {
showError(
'Please enter a valid price.'
);
return;
}
const total =
calculateTotal(price);
showResult(total);
}
function calculateTotal(price) {
const TAX_RATE = 0.18;
const tax =
price * TAX_RATE;
return price + tax;
}
function showResult(total) {
result.textContent =
`Total: ₹${total.toFixed(2)}`;
}
function showError(message) {
result.textContent = message;
}
}- initializeApplication is called before its declaration.
- Function declaration hoisting makes the call valid.
- DOM variables are declared before they are used.
- Nested function declarations are available throughout their function scope.
- const is used for values that should not be reassigned.
- Variables are kept inside the smallest practical scope.
- The code avoids unnecessary dependence on confusing hoisting behavior.
Hoisting Execution Flow
SOURCE CODE
│
▼
ENTER SCOPE
│
▼
┌────────────────────────────┐
│ CREATION PHASE │
│ │
│ var │
│ → initialize undefined │
│ │
│ let │
│ → create uninitialized │
│ │
│ const │
│ → create uninitialized │
│ │
│ function declaration │
│ → initialize full function │
│ │
│ class │
│ → create uninitialized │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ EXECUTION PHASE │
│ │
│ Run statements in order │
│ Perform assignments │
│ Initialize let and const │
│ Initialize classes │
│ Call functions │
│ Produce output │
└────────────────────────────┘Predicting Hoisting Output
A useful way to master hoisting is to identify every declaration before predicting the output.
console.log(value);
var value = 50;show();
function show() {
console.log('Hello');
}show();
const show = () => {
console.log('Hello');
};console.log(typeof calculate);
var calculate = function () {
return 10;
};- Identify the current scope.
- Find every declaration in that scope.
- Classify each declaration type.
- Determine its creation-phase state.
- Run the execution phase from top to bottom.
- Track every assignment.
- Check for TDZ access.
- Check whether a called value is actually a function.
Real-World Applications
Application Initialization
Function declarations can organize startup logic while keeping implementation details below.
Event Handling
Declared functions can be registered as event handlers before their declaration line.
Timers
Function declarations can be passed to timer APIs before their source declaration.
Modules
Static imports are prepared before the module body executes.
Function Organization
High-level workflow code can appear before lower-level function implementations.
Debugging
Understanding hoisting helps explain undefined values, ReferenceErrors, and TypeErrors.
Safer Variables
The TDZ prevents variables from being silently used before initialization.
Application Architecture
Clear declaration patterns make large JavaScript programs easier to understand.
Common Beginner Mistakes
- Believing JavaScript physically moves code to the top.
- Thinking only var is hoisted.
- Assuming let is not hoisted at all.
- Assuming const is not hoisted at all.
- Ignoring the difference between binding creation and initialization.
- Expecting var assignments to happen during hoisting.
- Using a var variable before assignment and expecting its final value.
- Confusing undefined with an undeclared variable.
- Accessing let before initialization.
- Accessing const before initialization.
- Ignoring the Temporal Dead Zone.
- Thinking the TDZ starts only one line before the declaration.
- Forgetting that the TDZ begins when the scope is entered.
- Using typeof on a TDZ variable and expecting undefined.
- Assuming all functions are hoisted the same way.
- Calling a var function expression before assignment.
- Calling a let function expression before initialization.
- Calling a const function expression before initialization.
- Treating arrow functions like function declarations.
- Calling arrow functions before their variable initialization.
- Confusing TypeError with ReferenceError.
- Ignoring the variable declaration type when debugging.
- Using classes before their declaration.
- Assuming class declarations behave like function declarations.
- Forgetting that class declarations have a TDZ.
- Creating confusing name conflicts between functions and variables.
- Using var and function declarations with the same name.
- Ignoring scope when analyzing hoisting.
- Assuming function-local variables are hoisted globally.
- Assuming block-scoped variables are available outside their block.
- Ignoring shadowing when debugging a TDZ error.
- Returning before required let or const initialization.
- Relying excessively on calling functions before their declarations.
- Writing code whose correctness depends on subtle hoisting behavior.
- Using var when const or let would be clearer.
- Declaring variables far away from their first use.
- Using variables before declaration simply because JavaScript allows it.
- Ignoring strict mode differences for block-level functions.
- Assuming imports behave exactly like normal variables.
- Memorizing rules without understanding creation and execution phases.
// ❌ Expecting final var value
console.log(price);
var price = 1000;
// ❌ Accessing let too early
// console.log(score);
let score = 100;
// ❌ Accessing const too early
// console.log(TAX_RATE);
const TAX_RATE = 0.18;
// ❌ Calling function expression early
// greet();
const greet = function () {
console.log('Hello');
};
// ❌ Calling arrow function early
// calculate();
const calculate = () => 100;
// ❌ Using class before declaration
// const user = new User();
class User {}
// ❌ Confusing shadowing and TDZ
const status = 'Global';
{
// console.log(status);
const status = 'Local';
}Best Practices
- Understand hoisting as declaration preparation rather than physical code movement.
- Separate the creation phase conceptually from the execution phase.
- Remember that var is initialized with undefined.
- Remember that var assignment happens during normal execution.
- Remember that let is hoisted but remains uninitialized.
- Remember that const is hoisted but remains uninitialized.
- Understand the Temporal Dead Zone.
- Declare variables before their first use.
- Prefer const when reassignment is not required.
- Use let when reassignment is necessary.
- Avoid var in modern JavaScript unless its behavior is specifically required.
- Do not intentionally depend on undefined from var hoisting.
- Keep declarations close to their first use.
- Initialize variables when they are declared whenever practical.
- Use descriptive variable names.
- Avoid unnecessary declaration and assignment separation.
- Understand that function declarations are fully initialized early.
- Use function declarations when declaration hoisting improves organization.
- Use function expressions when functions should behave like values.
- Do not call function expressions before initialization.
- Do not call arrow functions before initialization.
- Remember that arrow functions follow variable declaration rules.
- Understand the difference between ReferenceError and TypeError.
- Read exact browser error messages.
- Check the declaration type when debugging.
- Check for shadowing when a variable unexpectedly enters the TDZ.
- Avoid confusing function and variable name conflicts.
- Avoid reusing the same name for unrelated declarations.
- Keep variable scope as small as practical.
- Remember that hoisting happens within scope boundaries.
- Do not assume hoisting changes lexical scope.
- Declare classes before creating instances.
- Remember that classes are subject to the TDZ.
- Keep module imports organized at the top for readability.
- Do not rely on unusual import placement even when module semantics permit early availability.
- Use strict mode in traditional scripts.
- Remember that JavaScript modules are strict by default.
- Prefer predictable execution flow over clever hoisting tricks.
- Use comments only when execution order is genuinely non-obvious.
- Refactor code that repeatedly causes hoisting confusion.
const TAX_RATE = 0.18;
function calculateOrder(
price,
discountPercentage
) {
const discount =
calculateDiscount(
price,
discountPercentage
);
const discountedPrice =
price - discount;
const tax =
calculateTax(discountedPrice);
return discountedPrice + tax;
}
function calculateDiscount(
price,
percentage
) {
return price * percentage / 100;
}
function calculateTax(price) {
return price * TAX_RATE;
}
const total =
calculateOrder(1000, 10);
console.log(total);Frequently Asked Questions
What is hoisting in JavaScript?
Hoisting is the behavior produced because JavaScript creates declarations and bindings before executing normal statements in a scope.
Does JavaScript physically move declarations to the top?
No. JavaScript does not rewrite or physically move the source code. The engine prepares declarations before execution.
Why is hoisting important?
It explains why declarations behave differently before their source lines and helps developers understand undefined values, ReferenceErrors, and TypeErrors.
What are the two simplified phases of execution?
They are the creation phase and the execution phase.
What happens during the creation phase?
JavaScript prepares scope bindings for variables, functions, classes, and other declarations.
What happens during the execution phase?
JavaScript runs statements, performs assignments, calls functions, and produces results.
Is var hoisted?
Yes. A var binding is created and initialized with undefined before normal execution.
Why does var return undefined before assignment?
The binding exists and is initialized with undefined, but the assignment has not executed yet.
Is the value assigned to var hoisted?
No. The assignment happens at its original position during execution.
What is the difference between declaration and assignment?
A declaration creates a variable binding, while an assignment places a value into that binding.
Is let hoisted?
Yes. Its binding is created before execution, but it remains uninitialized until the declaration line is reached.
Is const hoisted?
Yes. Like let, its binding exists before execution but remains uninitialized until its declaration.
Why do let and const produce ReferenceError before declaration?
They are in the Temporal Dead Zone until execution reaches their declarations.
What is the Temporal Dead Zone?
The TDZ is the period from entering a scope until a let, const, or class binding is initialized.
When does the TDZ begin?
It begins when execution enters the scope containing the declaration.
When does the TDZ end?
It ends when execution reaches and initializes the declaration.
Does the TDZ apply inside blocks?
Yes. Block-scoped let and const variables are in the TDZ from the beginning of their block.
Can an inner TDZ variable shadow an outer variable?
Yes. The inner binding shadows the outer variable for the entire block, even before initialization.
Can typeof cause a ReferenceError?
Yes. Using typeof on a let, const, or class binding in the TDZ causes a ReferenceError.
Are function declarations hoisted?
Yes. Function declarations are initialized with their complete function values before normal execution.
Can I call a function declaration before writing it?
Yes. Function declarations can normally be called before their source declaration line.
Are function expressions hoisted?
The variable storing the expression follows its own hoisting rules, but the function value is not assigned until execution reaches the assignment.
What happens when a var function expression is called early?
The variable contains undefined, so calling it normally produces a TypeError.
What happens when a const function expression is called early?
It produces a ReferenceError because the variable is in the TDZ.
What happens when a let function expression is called early?
It produces a ReferenceError because the variable is in the TDZ.
Are arrow functions hoisted?
Arrow functions are expressions, so the variable storing them follows var, let, or const hoisting rules.
Can a const arrow function be called before declaration?
No. The variable is in the TDZ before initialization.
Can a var arrow function be called before assignment?
No. The variable contains undefined, so calling it produces a TypeError.
What is a named function expression?
It is a function expression that has an internal function name, often useful for recursion and debugging.
What is the main difference between a function declaration and function expression?
A function declaration is fully initialized before execution, while a function expression depends on when its variable receives the function value.
What happens when a function and var use the same name?
The function declaration can provide the initial function value, and a later var assignment can replace it during execution.
Does hoisting happen inside functions?
Yes. Declarations inside a function are prepared within that function scope.
Does hoisting make function variables global?
No. Hoisting never moves declarations outside their scope.
Does hoisting happen inside blocks?
Yes. Block-scoped declarations are prepared for their block and may enter the TDZ.
Are block-level functions scoped to blocks?
In modern strict-mode JavaScript, block-level function declarations are scoped to their block.
Are classes hoisted?
Yes, but class bindings remain uninitialized and are subject to the TDZ.
Can I create an object before its class declaration?
No. Accessing the class before initialization produces a ReferenceError.
How do class expressions behave?
They follow the hoisting behavior of the variable used to store them.
Are imports hoisted?
Static imports are processed before the module body executes.
Does hoisting change scope?
No. Every declaration remains inside its lexical scope.
Does hoisting change lexical scope?
No. Variable lookup still depends on where code is written.
What is the relationship between hoisting and closures?
Hoisting affects when bindings are created, while closures allow functions to continue accessing lexical bindings.
Why can var loops show undefined before the loop?
The var loop variable is created and initialized with undefined in its function or global scope before execution.
Can a function declaration be used as an event handler before its declaration?
Yes. Function declaration hoisting makes the function available.
Can a function declaration be passed to setTimeout before its declaration?
Yes. The complete function value is already available.
What causes ReferenceError in hoisting examples?
Common causes include accessing let, const, or class declarations while they are in the TDZ.
What causes TypeError in hoisting examples?
A common cause is attempting to call a var function expression before assignment, when its value is still undefined.
How can I debug a hoisting problem?
Identify the declaration type, scope, initialization point, and whether access occurs before initialization.
Should I rely on hoisting?
Function declaration hoisting can be useful, but variable code should generally be written in a clear order that does not depend on subtle hoisting behavior.
Should variables be declared before use?
Yes. Declaring variables before use makes code more predictable and easier to maintain.
Which variable declaration should I prefer?
Prefer const by default and use let when reassignment is required. Avoid var unless its specific behavior is needed.
Key Takeaways
- Hoisting does not physically move source code.
- JavaScript prepares declarations before normal execution.
- A simplified model divides execution into creation and execution phases.
- var bindings are initialized with undefined.
- var assignments happen during normal execution.
- let bindings are created but remain uninitialized.
- const bindings are created but remain uninitialized.
- let and const are subject to the Temporal Dead Zone.
- The TDZ begins when execution enters the relevant scope.
- The TDZ ends when the declaration is initialized.
- Accessing let or const in the TDZ produces a ReferenceError.
- typeof can produce a ReferenceError for a TDZ binding.
- Function declarations are fully initialized before normal execution.
- Function declarations can be called before their declaration line.
- Function expressions follow the rules of their variables.
- A var function expression contains undefined before assignment.
- Calling an unassigned var function expression produces a TypeError.
- let and const function expressions are in the TDZ before initialization.
- Arrow functions follow variable hoisting rules.
- Named function expressions have an internal function name.
- Function declarations and variable declarations can create name conflicts.
- Hoisting occurs within scope boundaries.
- Hoisting does not change lexical scope.
- Function calls create new execution contexts with their own declaration setup.
- Block-scoped declarations can create TDZ behavior inside blocks.
- Classes are hoisted but remain uninitialized.
- Class declarations are subject to the TDZ.
- Static imports are processed before module execution.
- ReferenceError and TypeError indicate different hoisting problems.
- Declarations should usually appear before their first use.
- Modern JavaScript should generally prefer const and let.
- Predictable code is better than clever dependence on hoisting.
Summary
Hoisting is the behavior that results from JavaScript preparing declarations before executing the normal statements in a scope.
Variables declared with var are initialized with undefined before execution, while their assignments happen later. This is why accessing var before assignment produces undefined instead of a ReferenceError.
Variables declared with let and const are also prepared before execution, but they remain uninitialized until their declaration lines. The period before initialization is called the Temporal Dead Zone.
Function declarations are fully initialized before execution and can therefore be called before their declaration lines. Function expressions and arrow functions instead follow the hoisting behavior of the variables that store them.
Classes are subject to the Temporal Dead Zone, and static imports are processed before the module body executes.
Understanding the creation phase, execution phase, initialization rules, scope boundaries, and the TDZ makes JavaScript hoisting predictable rather than mysterious.
- You understand what JavaScript hoisting is.
- You understand that source code is not physically moved.
- You understand the creation phase.
- You understand the execution phase.
- You understand what gets hoisted.
- You understand var hoisting.
- You understand why var contains undefined.
- You understand declaration versus assignment.
- You understand let hoisting.
- You understand const hoisting.
- You understand the Temporal Dead Zone.
- You understand how the TDZ works inside blocks.
- You understand typeof behavior with the TDZ.
- You understand function declaration hoisting.
- You can call function declarations before their source declaration.
- You understand function expression hoisting.
- You understand var function expression errors.
- You understand let and const function expression errors.
- You understand arrow function hoisting.
- You understand named function expressions.
- You understand function and variable name conflicts.
- You understand hoisting inside functions.
- You understand hoisting inside blocks.
- You understand block-level functions.
- You understand class hoisting.
- You understand import hoisting.
- You understand hoisting and scope.
- You understand hoisting and lexical scope.
- You understand common hoisting errors.
- You can debug ReferenceError and TypeError problems.
- You are ready to learn JavaScript Strings.