DEV Community

Gaurav Singh
Gaurav Singh

Posted on

# JavaScript Hoisting — What Actually Happens Behind the Scenes ?

Demystifying Hoisting: What Really Happens Inside the JavaScript Engine

Most JavaScript developers have heard the common statement: "JavaScript moves variables and functions to the top of your file." While this explanation is widespread, it is technically incorrect. Hoisting is not code movement. Nothing is physically shifted around in your source file. Instead, hoisting is the direct result of how the JavaScript engine prepares memory before executing a single line of your code.

Once you understand what happens internally during this preparation step, hoisting becomes one of the easiest JavaScript concepts to grasp.


Why Hoisting Exists: The Two-Pass Model

JavaScript does not just execute your code immediately, line by line. Before execution starts, the JavaScript engine performs a crucial preparation step where it scans your code, identifies variables and functions, and allocates memory for them.

This process happens in two distinct phases:

  1. Memory Creation Phase: The engine scans the code and allocates memory slots for all declarations. This is when hoisting happens.
  2. Execution Phase: The engine runs your code line by line, assigning values and executing functions.

A Simple Real-Life Analogy

Imagine students entering an examination hall. Before they begin writing their papers:

  • Answer sheets are distributed.
  • Roll numbers are assigned to desks.
  • Seating arrangements are completed.

Only after all preparations are finished does the actual exam begin. JavaScript behaves exactly the same way. Before executing code, variables and functions are discovered, and memory space is allocated.


Defining Hoisting Accurately

  • The Beginner-Friendly Definition: Hoisting is JavaScript's behavior of making declarations available before execution starts.
  • The Technically Accurate Definition: Hoisting is the memory allocation process performed during the creation phase of an Execution Context.

Deep Dive: 4 Core Examples of Hoisting

Example 1: Variable Hoisting with var

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

Enter fullscreen mode Exit fullscreen mode

Output:

undefined

Enter fullscreen mode Exit fullscreen mode

Many beginners expect a ReferenceError here. Why does JavaScript print undefined instead?

What Happens Internally?

During the Memory Creation Phase, the engine encounters var a = 10;. It splits this into declaration and assignment, allocates memory for a, and automatically initializes it with a default value of undefined.

Variable Initial Value
a undefined

When the Execution Phase starts:

  • Step 1: console.log(a); runs. At this exact moment, a holds the value undefined.
  • Step 2: a = 10; runs, updating the value in memory from undefined to 10.

This is why var variables can be accessed before their formal declaration without throwing an error.


Example 2: Function Hoisting

sayHello();

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

Enter fullscreen mode Exit fullscreen mode

Output:

Hello

Enter fullscreen mode Exit fullscreen mode

Function declarations are treated differently from variables. During the creation phase, JavaScript stores the entire function definition in memory right away.

Name Stored Value
sayHello function sayHello() { ... } (Complete Function)

Because the complete function is fully hoisted and available before execution begins, you can safely invoke it at the very top of your script.


Example 3: Function Expressions

What happens when you combine a function with a variable declaration?

sayHi();

var sayHi = function () {
  console.log("Hi");
};

Enter fullscreen mode Exit fullscreen mode

Output:

TypeError: sayHi is not a function

Enter fullscreen mode Exit fullscreen mode

This surprises many developers. Why does it fail?

During the Memory Creation Phase, the engine treats var sayHi like any other variable. It allocates memory and initializes it to undefined. The function assignment itself is ignored until the execution phase.

Variable Value
sayHi undefined

During execution, the engine reaches sayHi(); and translates it to undefined();. Because undefined is not a callable function data type, JavaScript throws a TypeError. The variable container is hoisted, but the function assignment is not.


Example 4: Hoisting with let and const

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

Enter fullscreen mode Exit fullscreen mode

Output:

ReferenceError: Cannot access 'a' before initialization

Enter fullscreen mode Exit fullscreen mode

At first glance, it appears that let and const are not hoisted. But that is a misconception—they are hoisted.

When the engine scans code during the memory phase, it allocates space for let and const variables. However, unlike var, they are not initialized with undefined. Instead, they are placed in an uninitialized state known as the Temporal Dead Zone (TDZ).

Variable State
a Uninitialized (Temporal Dead Zone)

Attempting to read or write to a variable while it sits in the TDZ results in a ReferenceError. The variable only becomes safely usable after the execution flow reaches the line where it is formally initialized.


Quick Reference Summary

The following tables break down how different declarations are treated during the internal memory creation phase.

Declaration Behaviors

Feature var let const
Hoisted Yes Yes Yes
Initial Value undefined Uninitialized Uninitialized
Access Before Dec. undefined ReferenceError ReferenceError
Scope Function Block Block

Memory Phase Blueprint

Type Memory Phase State
var undefined
function Stores Entire Function
let uninitialized (TDZ)
const uninitialized (TDZ)

How JavaScript Executes Code: Behind the Scenes

To truly tie this all together, we have to look at the execution environment itself. When a web browser encounters a script tag, it passes the source code down to its respective engine (such as Google Chrome's V8, Mozilla Firefox's SpiderMonkey, or Safari's JavaScriptCore).

The engine processes your file using a mechanism called the Execution Context—an environment wrapper that manages the execution variables, functions, scopes, and the this evaluation.

The Call Stack

JavaScript handles execution contexts using a Call Stack. Think of it as a stack of dishes where the last item added is the first one to be cleared:

function one() {
  two();
}
function two() {
  console.log("Hello");
}
one();

Enter fullscreen mode Exit fullscreen mode
  1. Global Execution Context is created and pushed to the bottom of the stack when the script loads.
  2. one() is invoked $\rightarrow$ Its execution context is pushed onto the stack.
  3. two() is invoked inside one() $\rightarrow$ Its execution context is pushed to the top.
  4. two() finishes $\rightarrow$ Popped off the stack.
  5. one() finishes $\rightarrow$ Popped off the stack.

Complete Internal Flow Example

Let's look at a complete program to see how memory creation and execution phases pair together:

var x = 5;
function square(n) {
  return n * n;
}
var result = square(x);
console.log(result);

Enter fullscreen mode Exit fullscreen mode

Phase 1: Memory Creation

Before running line one, the global memory environment maps out your declarations:

  • x is registered and set to undefined.
  • square is registered, storing its entire block logic.
  • result is registered and set to undefined.

Phase 2: Code Execution

  • Line 1: x is assigned the value 5.
  • Line 2: The engine skips over the function declaration since it's already stored.
  • Line 3: square(x) is evaluated. A new local function execution context is spawned on the Call Stack, passing 5 into the parameter n. It computes $5 \times 5$, returns 25, and destroys its local environment context. result is updated to 25.
  • Line 4: Outputs 25 directly to the console.

Wrapping Up: The Ultimate Mental Model

Whenever your JavaScript programs run, remember this four-step mental checklist:

  1. Create an Execution Context.
  2. Scan for variable and function declarations.
  3. Allocate memory slots (initializing variables based on their keywords).
  4. Execute the remaining source code line by line.

Hoisting isn't a magical code-lifting feature. It is simply the JavaScript engine taking a moment to set up its memory grid before letting your program loose!

Top comments (0)