A Comprehensive Guide to Understanding Hoisting in JavaScript

Category: Technology | Published: 3 months ago

Listen to article podcast:

A Comprehensive Guide to Understanding Hoisting in JavaScript

Introduction to Hoisting

Hoisting is one of the most important but often misunderstood concepts in JavaScript. It refers to JavaScript's behavior of moving declarations (variables, functions, etc.) to the top of their respective scopes (global or local) during the compilation phase, before the code executes. This allows developers to reference functions and variables before their actual declaration in the code, which can lead to unexpected results if not understood correctly.

In this guide, we will demystify hoisting by exploring its behavior with different types of declarations, how it works in JavaScript's two-phase execution model, and share best practices to help you avoid common pitfalls.


Key Concepts of Hoisting

1. Declarations vs. Initializations

It is crucial to understand that only declarations are hoisted, not initializations. This means that when a variable or function is declared in the code, the declaration is moved to the top of the scope. However, any value assignment or initialization occurs where it is explicitly written in the code.

For example:

console.log(myVar); // Output: undefined
var myVar = 10;
console.log(myVar); // Output: 10

In the above code, the declaration

var myVar
is hoisted, but the assignment
myVar = 10
is not. This is why
undefined
is logged before the assignment.

2. Scope of Hoisting

Hoisting operates within the global scope or the local scope (inside functions or blocks). Regardless of where the declaration occurs, it is hoisted to the top of its scope. In a function, variables and function declarations within the function are hoisted to the top of that function's scope.

3. Types of Declarations

JavaScript provides three ways to declare variables: var, let, and const. Each of these behaves differently under hoisting:

  • var: Declarations are hoisted and initialized with undefined. This means you can reference them before they are defined, but their value will be undefined until the assignment happens.

  • let and const: Declarations are hoisted, but they are not initialized. Accessing them before the declaration results in a ReferenceError because they exist in a "temporal dead zone" (TDZ) from the start of the block until they are declared and initialized.


JavaScript's Compilation and Execution Phases

JavaScript is an interpreted language, but before code execution, it goes through a compilation phase followed by an execution phase.

1. Compilation Phase

During the compilation phase, the JavaScript engine scans the code for all variable and function declarations and hoists them to the top of their respective scopes. At this point:

  • Variables declared using var are initialized to undefined.

  • Variables declared with let and const are hoisted but remain uninitialized until they are executed.

  • Function declarations are fully hoisted, meaning the entire function definition is moved to the top.

2. Execution Phase

In this phase, the JavaScript engine executes the code line by line:

  • Initializations for var-declared variables occur where they are written in the code.

  • For let and const, the code execution encounters these variables only at the point of their explicit declaration, not before.

  • Hoisted functions are available for invocation from the very start of the scope.


Hoisting with Variables and Functions: Practical Examples

1. Variable Hoisting with
var

In this example, the

var
declaration is hoisted to the top, but the initialization remains in place:

console.log(myVar); // Output: undefined
var myVar = 5;
console.log(myVar); // Output: 5

Here, myVar is declared at the top of its scope, but it is initialized to

undefined
before the assignment to 5 happens.

2. Variable Hoisting with
let
and
const

Variables declared with

let
and
const
exhibit a different hoisting behavior:

console.log(myLet);  // Output: ReferenceError: myLet is not defined
let myLet = 10;

console.log(myConst);  // Output: ReferenceError: myConst is not defined
const myConst = 20;

In this case, trying to access

myLet
or
myConst
before their declaration throws a ReferenceError. This occurs because they are in the temporal dead zone—the region between the start of the block and the point where the variable is declared.

3. Function Hoisting

Unlike variables, function declarations are fully hoisted. This means the entire function definition is available from the start of the scope, and you can call a function before it is defined in the code:

hoistedFunction();  // Output: "I am hoisted!"
function hoistedFunction() {
    console.log("I am hoisted!");
}

However, function expressions (functions assigned to

var
,
let
, or
const
) do not benefit from this behavior:

hoistedFunction();  // Output: TypeError: hoistedFunction is not a function
var hoistedFunction = function() {
    console.log("I am not hoisted!");
};

In this case,

hoistedFunction
is treated as a variable, so only the variable declaration is hoisted. The function assignment occurs later, leading to a
TypeError
when trying to invoke it beforehand.


Best Practices to Avoid Hoisting Pitfalls

While hoisting is a powerful feature of JavaScript, it can lead to confusion and bugs if not carefully managed. Here are some best practices to follow:

  1. Declare Variables at the Top of Their Scope: To avoid hoisting surprises, always declare variables at the top of their respective scopes. This makes it clear when variables are being initialized and reduces potential errors caused by hoisting.

  2. Use let and const Instead of var: let and const provide better scoping rules and help prevent bugs related to hoisting. const should be preferred for variables that do not change, while let is ideal for those that might be reassigned.

  3. Understand the Temporal Dead Zone: Be mindful of the temporal dead zone with let and const. Knowing that these variables are not initialized until their declaration helps prevent common errors when accessing them too early.

  4. Keep Function Declarations Consistent: While function declarations are hoisted, mixing function expressions and declarations can cause confusion. Stick to one style to make code behavior more predictable.


Conclusion

Hoisting is an essential aspect of how JavaScript functions under the hood. By moving declarations to the top of their scope during compilation, JavaScript allows variables and functions to be used before their actual declaration. Understanding the nuances between var, let, and const, as well as function declarations, can help you write clearer, more reliable code.

With a solid grasp of hoisting, you’ll be better equipped to avoid common pitfalls, such as undefined values or ReferenceErrors, making your JavaScript code more predictable and easier to debug.


References:

  1. StudySmarter: JavaScript Hoisting Explained

  2. W3Schools: JavaScript Hoisting

  3. DigitalOcean: Understanding Hoisting in JavaScript

A Comprehensive Guide to Understanding Hoisting in JavaScript
Kiran Chaulagain

Kiran Chaulagain

kkchaulagain@gmail.com

I am a Full Stack Software Engineer and DevOps expert with over 6 years of experience. Specializing in creating innovative, scalable solutions using technologies like PHP, Node.js, Vue, React, Docker, and Kubernetes, I have a strong foundation in both development and infrastructure with a BSc in Computer Science and Information Technology (CSIT) from Tribhuvan University. I’m passionate about staying ahead of industry trends and delivering projects on time and within budget, all while bridging the gap between development and production. Currently, I’m exploring new opportunities to bring my skills and expertise to exciting new challenges.