You are currently viewing JavaScript Hoisting Explained Simply
Hoisting

JavaScript Hoisting Explained Simply

JavaScript is known for being quirky and flexible—traits that can make it both exciting and frustrating to learn. One of the most misunderstood features of the language is hoisting. It confuses many developers, especially beginners, because it doesn’t behave like you’d expect based on how code is written.

In this article, we’ll unpack JavaScript hoisting in the simplest way possible. We’ll walk through what hoisting really is, how it works behind the scenes, and why understanding it is essential to becoming a better JavaScript developer.

JavaScript Promises: How They Work and Why They Matter


What is Hoisting in JavaScript?

Hoisting is a JavaScript mechanism where variable and function declarations are moved to the top of their scope (either global or function scope) before the code is executed.

Let’s be clear: only declarations are hoisted—not initializations or assignments.

This means you can use certain variables and functions before they are declared in the code. Here’s a quick example:

console.log(greeting); // undefined
var greeting = "Hello!";

You might expect this to throw an error because greeting is used before it is declared. But instead, JavaScript prints undefined. That’s hoisting in action.


Behind the Scenes: What Really Happens?

When the JavaScript engine compiles your code, it does two things:

  1. Creates memory space for variables and functions.
  2. Executes the code line by line.

During the first phase (also called the creation phase), JavaScript scans your code and hoists all variable and function declarations to the top of their scope. Only declarations, not initializations.

The previous example is interpreted by the engine like this:

var greeting;
console.log(greeting); // undefined
greeting = "Hello!";

Understanding this mental model helps you avoid strange bugs and surprises.


Function Hoisting

Functions in JavaScript are hoisted differently than variables. Specifically, function declarations are hoisted with their entire definition. This means you can call a function before you define it in your code.

sayHello(); // "Hello, world!"

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

This works perfectly fine because the function declaration sayHello() is hoisted along with its implementation.


Function Expressions Are Not Hoisted the Same Way

Let’s look at this example:

sayHi(); // TypeError: sayHi is not a function

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

Why does this throw an error?

Because sayHi is a variable, and only the declaration var sayHi is hoisted. The function assignment isn’t. So this is how the engine sees it:

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

At the time of the function call, sayHi is just a variable with the value undefined.


Variable Hoisting: var, let, and const

var is Fully Hoisted (But Only Declared)

When you use var, the variable is hoisted and initialized with undefined.

console.log(score); // undefined
var score = 100;

Equivalent to:

var score;
console.log(score); // undefined
score = 100;

This can lead to bugs if you’re not careful, especially in large codebases or when using the same variable name in multiple scopes.


let and const Are Hoisted Too (But in a Different Way)

Contrary to popular belief, let and const are also hoisted. However, they are not initialized during the hoisting phase. Instead, they remain in a “temporal dead zone” (TDZ) from the start of the block until the declaration is encountered.

console.log(name); // ReferenceError
let name = "Alice";

This behavior is intentional—it prevents you from accessing variables before they are safely initialized.


Understanding the Temporal Dead Zone (TDZ)

The Temporal Dead Zone is the period between the start of a block scope and when a variable is declared using let or const.

Accessing the variable during this time results in a ReferenceError, not undefined.

{
    // TDZ starts here
    console.log(age); // ReferenceError
    let age = 30;
}

This rule encourages safer, more predictable code compared to var.


Real-World Examples Where Hoisting Can Hurt

Example 1: Accidental Overwrites

var value = 10;

function test() {
    console.log(value); // undefined
    var value = 20;
}

test();

You might think this would print 10 because value is declared outside. But inside the function, var value is hoisted to the top of the function scope, so you’re logging the local (and still undefined) variable.

Rewritten by the JavaScript engine:

function test() {
    var value;
    console.log(value); // undefined
    value = 20;
}

Example 2: Misplaced Function Expressions

runTask(); // TypeError

var runTask = function() {
    console.log("Running...");
};

This causes confusion because developers often expect function-like behavior. But since this is a function expression, it behaves like a variable and only the declaration is hoisted.


Best Practices to Avoid Hoisting Confusion

1. Use let and const Instead of var

Modern JavaScript development discourages the use of var. Stick with let and const for block-scoped and more predictable behavior.

// Bad
var count = 5;

// Good
let count = 5;
const PI = 3.14;

2. Always Declare Variables at the Top of Their Scope

While hoisting allows you to declare variables anywhere, it’s a good habit to declare them at the beginning of the function or block to improve readability.

3. Don’t Use Functions Before Declaring Them (Even if You Can)

Just because you can call a function before it’s defined doesn’t mean you should. Declaring functions before usage leads to cleaner and more predictable code.

4. Avoid Re-declaring Variables

Especially with var, re-declarations can happen silently, leading to unpredictable bugs.

var a = 10;
var a = 20; // No error, but risky

let b = 10;
let b = 20; // SyntaxError

Deep Dive: Hoisting in Function vs Block Scope

Hoisting behaves differently depending on the scope.

Function Scope

In a function, var declarations are hoisted to the top of the function:

function demo() {
    console.log(num); // undefined
    var num = 5;
}

Equivalent to:

function demo() {
    var num;
    console.log(num); // undefined
    num = 5;
}

Block Scope

Variables declared with let or const inside a block are not accessible outside and follow the TDZ rule:

{
    console.log(item); // ReferenceError
    let item = "Apple";
}

Visualizing Hoisting

A helpful way to internalize hoisting is to imagine the compiler reordering your code:

// What you write
function show() {
    console.log(msg);
    var msg = "Hello!";
}

JavaScript engine transforms it like this:

function show() {
    var msg;
    console.log(msg); // undefined
    msg = "Hello!";
}

Once you start visualizing this internally, hoisting becomes much easier to grasp and predict.


Summary of Key Points

ConceptBehavior
varHoisted and initialized to undefined
let and constHoisted but not initialized (TDZ)
Function DeclarationsFully hoisted with definition
Function ExpressionsOnly variable is hoisted
TDZPeriod when let/const exists but is not accessible
Best PracticeUse let/const, declare before use, avoid var

Conclusion

Hoisting in JavaScript is one of those tricky concepts that becomes surprisingly simple once you understand how the language processes your code. The key takeaway is this: declarations are hoisted, not initializations.

By understanding how and why hoisting works, you can write cleaner, safer, and more predictable code. It helps eliminate subtle bugs, makes debugging easier, and improves code readability.

Whether you’re just starting out or revisiting JavaScript after a while, mastering hoisting is a vital step toward writing professional, error-free JavaScript.


Let me know if you’d like this article in PDF, DOCX format, or need help publishing it to a blog or portfolio!