LearnContact
Lesson 1545 min read

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 You Will Learn
  • 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.

Basic Hoisting Example
console.log(message);

var message = 'Hello JavaScript';
Output

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.

Conceptual Representation
var message;

console.log(message);

message = 'Hello JavaScript';
Output
Important
  • 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.

Hotel Reservation Analogy
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 BEGINS

The 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 Execution Process
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.

During the Creation Phase
  • 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.
Source Code
console.log(score);

showMessage();

var score = 100;

function showMessage() {
    console.log('Hello!');
}
Simplified Creation Phase
CREATION PHASE

score
    created
    value = undefined

showMessage
    created
    value = complete function

No console.log executed yet
No score = 100 assignment yet

Execution Phase

During the execution phase, JavaScript runs statements from top to bottom and performs assignments and function calls.

Execution Phase
START EXECUTION
      │
      ▼
console.log(score)
      │
      ▼
Print undefined
      │
      ▼
showMessage()
      │
      ▼
Print Hello!
      │
      ▼
score = 100
      │
      ▼
END
Output

What Actually Gets Hoisted?

Hoisting Overview
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
The Important Question
  • 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.

Different Variable Behaviors
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.

var Before Declaration
console.log(name);

var name = 'Aarav';

console.log(name);
Output

The declaration exists before the first console.log, but the assignment to Aarav happens only when execution reaches that line.

How var Hoisting Works

Original Code
console.log(price);

var price = 1500;

console.log(price);
Step-by-Step Process
CREATION PHASE

price = undefined


EXECUTION PHASE

Step 1
console.log(price)
Output: undefined

Step 2
price = 1500

Step 3
console.log(price)
Output: 1500
Output

var 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.

Declaration and Assignment
var course = 'JavaScript';
Conceptual Separation
DECLARATION

var course;


ASSIGNMENT

course = 'JavaScript';
Remember
  • 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.

Multiple var Declarations
console.log(value);

var value = 10;
var value = 20;

console.log(value);
Output

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-Level var Hoisting
function showScore() {
    console.log(score);

    var score = 100;

    console.log(score);
}

showScore();
Output
Outside the Function
function showScore() {
    var score = 100;
}

showScore();

console.log(score);
Error

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.

let Before Declaration
console.log(score);

let score = 100;
Error
let is Different from var
  • 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.

const Before Declaration
console.log(PI);

const PI = 3.14159;
Error

A const declaration must also receive a value when it is declared.

const Requires Initialization
const total;
Error

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.

Temporal Dead Zone
// TDZ starts for userName

console.log(userName);

// TDZ ends here
let userName = 'Priya';

console.log(userName);
Error
TDZ Timeline
ENTER SCOPE
     │
     ▼
┌─────────────────────────┐
│ TEMPORAL DEAD ZONE      │
│                         │
│ userName exists         │
│ but cannot be accessed  │
│                         │
│ Access = ReferenceError │
└────────────┬────────────┘
             │
             ▼
let userName = 'Priya'
             │
             ▼
TDZ ENDS
             │
             ▼
userName is accessible

How the TDZ Works

The TDZ begins when execution enters the variable’s scope. It ends when execution reaches the declaration and initialization.

TDZ Process
{
    // TDZ begins

    console.log(status);

    // TDZ ends
    let status = 'Active';

    console.log(status);
}
Error

TDZ with let

let TDZ
function showPrice() {
    console.log(price);

    let price = 1500;

    console.log(price);
}

showPrice();
Error

TDZ with const

const TDZ
function showTax() {
    console.log(TAX_RATE);

    const TAX_RATE = 0.18;
}

showTax();
Error

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.

Block TDZ and Shadowing
const message = 'Global Message';

{
    console.log(message);

    const message = 'Block Message';
}
Error

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.

typeof Undeclared Variable
console.log(typeof unknownVariable);
Output
typeof Variable in TDZ
console.log(typeof score);

let score = 100;
Error

var vs let vs const Hoisting

Variable Hoisting Comparison
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        Yes

Function 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.

Function Declaration Hoisting
greet();

function greet() {
    console.log('Hello, JavaScript!');
}
Output
Creation Phase
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.

Call Before Declaration
const result = add(10, 20);

console.log(result);

function add(first, second) {
    return first + second;
}
Output
Useful but Use Clearly
  • 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.

Function Expression
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.

var Function Expression
greet();

var greet = function () {
    console.log('Hello!');
};
Error

The variable greet exists, but its value is undefined when the call occurs.

Why TypeError Occurs
CREATION PHASE

greet = undefined


EXECUTION PHASE

greet()
   │
   ▼
undefined()
   │
   ▼
TypeError

let Function Expressions

let Function Expression
greet();

let greet = function () {
    console.log('Hello!');
};
Error

const Function Expressions

const Function Expression
greet();

const greet = function () {
    console.log('Hello!');
};
Error

Arrow Function Hoisting

Arrow functions are expressions. Their behavior before declaration depends on the variable used to store them.

const Arrow Function
calculate();

const calculate = () => {
    console.log('Calculating...');
};
Error
var Arrow Function
calculate();

var calculate = () => {
    console.log('Calculating...');
};
Error
Arrow Function Rule
  • 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.

Named Function Expression
const factorial = function calculate(number) {
    if (number <= 1) {
        return 1;
    }

    return number * calculate(number - 1);
};

console.log(factorial(5));
Output

The name calculate is available inside the function body. The outer variable factorial follows const rules.

Function Declaration vs Function Expression

Function Hoisting Comparison
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       ReferenceError

Function and Variable Name Conflicts

When function declarations and var declarations use the same name, the creation process can produce behavior that appears surprising.

Function and var Conflict
console.log(typeof processData);

var processData = 100;

function processData() {
    return 'Processing';
}

console.log(typeof processData);
Output

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.

Hoisting Priority Example
console.log(typeof test);

var test;

function test() {
    return 'Hello';
}

console.log(typeof test);
Output

Hoisting Inside Functions

Every function call creates its own execution context and prepares declarations inside that function.

Hoisting Inside Function
function calculate() {
    console.log(total);

    var total = 500;

    console.log(total);
}

calculate();
Output
Function Execution Context
CALL calculate()
       │
       ▼
CREATE FUNCTION CONTEXT
       │
       ▼
total = undefined
       │
       ▼
EXECUTE FUNCTION BODY
       │
       ├── Print undefined
       │
       ├── total = 500
       │
       └── Print 500

Hoisting Inside Blocks

let and const declarations belong to their block and enter the TDZ from the beginning of that block.

Block Hoisting
let status = 'Global';

{
    console.log(status);

    let status = 'Block';
}
Error

Block-Level Function Declarations

In modern strict-mode JavaScript, a function declaration inside a block is scoped to that block.

Block-Level Function
'use strict';

if (true) {
    function showMessage() {
        console.log('Inside Block');
    }

    showMessage();
}

showMessage();
Output
Avoid Legacy Ambiguity
  • 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

Class Before Declaration
const user = new User('Aarav');

class User {
    constructor(name) {
        this.name = name;
    }
}
Error
Correct Class Usage
class User {
    constructor(name) {
        this.name = name;
    }
}

const user = new User('Aarav');

console.log(user.name);
Output

Class Expression Hoisting

A class expression follows the hoisting rules of the variable that stores it.

const Class Expression
const user = new User('Priya');

const User = class {
    constructor(name) {
        this.name = name;
    }
};
Error

Import Hoisting

Static import declarations are processed before the module body executes. Imported bindings are available throughout the module according to module initialization rules.

app.js
console.log(calculateTotal(100, 200));

import {
    calculateTotal
} from './math.js';
math.js
export function calculateTotal(
    first,
    second
) {
    return first + second;
}
Output
Module Imports
  • 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 Scope Hoisting
function first() {
    console.log(value);

    var value = 10;
}

function second() {
    console.log(value);

    var value = 20;
}

first();
second();
Output

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.

Hoisting and Lexical Scope
const value = 'Global';

showValue();

function showValue() {
    console.log(value);
}
Output

Hoisting and Closures

Closures remember lexical bindings. Hoisting determines when those bindings are created, while closures determine how functions continue accessing them.

Hoisting and Closure
function createCounter() {
    return increase;

    let count = 0;

    function increase() {
        count++;

        return count;
    }
}

const counter = createCounter();

console.log(counter());
Error

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.

Correct Closure
function createCounter() {
    let count = 0;

    return increase;

    function increase() {
        count++;

        return count;
    }
}

const counter = createCounter();

console.log(counter());
console.log(counter());
Output

Hoisting in Loops

Loop declarations follow their normal scope and hoisting rules. var creates one function-scoped binding, while let creates iteration-specific bindings.

var Loop
console.log(i);

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

console.log(i);
Output
let Loop
console.log(i);

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

Hoisting with Event Handlers

Function declarations can be registered as event handlers before their declaration appears in the source code.

index.html
<button id="showButton">
    Show Message
</button>

<p id="output"></p>
script.js
const button =
    document.getElementById('showButton');

button.addEventListener(
    'click',
    showMessage
);

function showMessage() {
    const output =
        document.getElementById('output');

    output.textContent =
        'JavaScript function hoisting works!';
}
Browser Output

Hoisting with setTimeout

Function Declaration with Timer
setTimeout(showMessage, 1000);

function showMessage() {
    console.log('Message after 1 second');
}
Output After 1 Second
Function Expression with Timer
setTimeout(showMessage, 1000);

const showMessage = function () {
    console.log('Hello');
};
Error

Common Hoisting Errors

Common Error Types
ERROR TYPE
    │
    ├── ReferenceError
    │      let before declaration
    │      const before declaration
    │      class before declaration
    │      undeclared variable
    │
    └── TypeError
           calling undefined
           var function expression
           before assignment
ReferenceError Example
console.log(total);

let total = 100;
Error
TypeError Example
calculate();

var calculate = function () {
    return 100;
};
Error

Debugging Hoisting Problems

Debugging Checklist
  • 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.
Problematic Code
function calculateOrder() {
    console.log(total);

    const price = 1000;
    const tax = 180;

    let total = price + tax;

    return total;
}

calculateOrder();
Error
Fixed Code
function calculateOrder() {
    const price = 1000;
    const tax = 180;

    const total = price + tax;

    console.log(total);

    return total;
}

calculateOrder();
Output

Complete Hoisting Example

The following example demonstrates function declaration hoisting, variable initialization, block scope, and predictable declaration order.

index.html
<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>
script.js
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;
    }
}
Browser Output
What This Example Demonstrates
  • 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

Complete Hoisting 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.

Example 1
console.log(value);

var value = 50;
Output
Example 2
show();

function show() {
    console.log('Hello');
}
Output
Example 3
show();

const show = () => {
    console.log('Hello');
};
Error
Example 4
console.log(typeof calculate);

var calculate = function () {
    return 10;
};
Output
Prediction Method
  • 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

Avoid These 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.
Common Hoisting Mistakes
// ❌ 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.
Well-Structured Code
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);
Output

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.

Lesson 15 Completed
  • 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.
Next Lesson →

JavaScript Strings