Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 3

Understanding Hoisting: Variables and Functions

Recommended resources

Introduction

Welcome to our next lesson. In our previous session, we established that the JavaScript engine processes code within an execution context, which has two distinct phases: Creation and Execution. Today, we'll explore a direct and often misunderstood consequence of that two-phase process: hoisting. This concept is fundamental to understanding how JavaScript organizes and runs your code.

By the end of this lesson, you will be able to accurately predict the behavior of variables and functions in code snippets, even when they are accessed before they appear to be declared. This is a core competency for any proficient JavaScript developer and a frequent topic in technical interviews. We will connect the theoretical model of the execution context to the practical behavior you observe, demystifying what can often seem like magic.


What is Hoisting?

In many programming languages, trying to use a variable or function before its declaration would result in an immediate error. JavaScript, however, behaves differently. Let's look at a classic example:

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

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

Notice that we can access myVar before its assignment without a crash, and we can call sayHello() before its definition. This is hoisting in action. It's often described as "moving declarations to the top," but this is just a metaphor. The reality, as we learned in the last lesson, lies in the memory creation phase.

Before executing a single line of code, the JavaScript engine scans the entire scope (global or functional) and allocates memory for all variable and function declarations. Hoisting is the observable effect of this preparation phase.

Let's watch a video that vividly illustrates this process and connects it directly to our previous lesson on execution contexts.

Hoisting in JavaScript 🔥(variables & functions) | Namaste JavaScript Ep. 3

This video from the "Namaste JavaScript" series provides an excellent, in-depth explanation of hoisting by connecting it to the underlying mechanics of the execution context.

Please watch the following segments: The Introduction: The presenter demonstrates the "magic" of hoisting and introduces the key puzzle: why does an undeclared variable log undefined, while a non-existent one throws a ReferenceError? Memory Creation Phase: This is the core of the explanation. Pay close attention to how the presenter uses the browser debugger to show that before any code runs, memory is already allocated for var variables (with a placeholder of undefined) and for function declarations (with the entire function code). undefined vs. not defined: This segment clarifies the crucial difference between a variable that has been hoisted but not yet assigned a value, and a variable that has no memory allocated for it at all.

As the video demonstrates, hoisting isn't about code being moved. It's about how memory is set up during the creation phase. Let's formalize how this works for different types of declarations.


Hoisting Behavior by Declaration Type

The way hoisting behaves depends on whether you are using a function declaration, var, let, or const.

This visual gives a high-level summary. Functions and `var` are "lifted up," but `let` and `const` are locked by the TDZ (Temporal Dead Zone), which we'll explore next.

To really master this, let's work through some practical code snippets. The ability to predict the output of these is the main goal of this lesson.

"Master JavaScript Hoisting: 10 Tricky Output Questions ...

This article provides a series of "predict the output" questions that are perfect for building practical skill. We will analyze three key scenarios.

For each of the following sections, first try to predict the output of the code yourself, then read the explanation. Read Variable Hoisting with var. This shows the most basic case, where a var declaration is hoisted and initialized with undefined. Now read Hoisting with Function Declaration. This demonstrates that function declarations are hoisted in their entirety, including their implementation. Finally, read Hoisting with Function Expressions. This is a critical point of comparison. Notice that when a function is assigned to a var, it behaves like a variable, not a function declaration. The variable foo is hoisted and is undefined initially, leading to a TypeError if you try to invoke it before the assignment.

The distinction between a function declaration (function name() {}) and a function expression (var name = function() {}) is a classic source of confusion, and hoisting is the reason for their different behaviors. A function declaration is fully available before execution, while a function expression is not.


The Temporal Dead Zone: let and const

A common misconception is that variables declared with let and const are not hoisted. This is incorrect. They are hoisted, but they behave differently from var.

let and const declarations are hoisted to the top of their block, but they are not initialized with any value. They exist in a state known as the Temporal Dead Zone (TDZ) from the start of the block until the point of their declaration. Any attempt to access them within the TDZ results in a ReferenceError.

This is a deliberate language feature designed to prevent bugs that can arise from using a variable before its intended initialization.

JavaScript Hoisting Explained: var, let, const & Functions

This article provides a very clear definition and visualization of the Temporal Dead Zone.

Please read the sections let and const Hoisting and The Temporal Dead Zone (TDZ). Focus on the difference between the ReferenceError you get with let/const and the undefined you get with var.

To see this in action within the debugger, let's turn to another practical demonstration.

Javascript Interview Questions ( Var, Let and Const ) - Hoisting, Scoping, Shadowing and more

The presenter in this video uses the debugger to show exactly where let variables live during the TDZ.

Watch the segment from let variables and TDZ. Notice how var declarations appear on the global object (or function scope), whereas the hoisted let variable is shown in a separate "Script" scope and is inaccessible. The video then explains this as the TDZ. Then, watch the follow-up example from interview question example, which demonstrates the practical difference between var, let, and const hoisting within a function.


Summary of Hoisting Rules

We've covered several rules. This table provides a concise summary of the behavior for each declaration type.

A detailed table comparing hoisting behavior for `var`, `let`, function declarations, function expressions, and classes. It highlights whether they are hoisted, their initialization state, and the value you get when accessing them before declaration.

As you can see, function (expression) and class behave like let/const in that they are hoisted but inaccessible before their declaration line, resulting in an error.


Test Your Understanding

Let's put everything together with a final challenge. Analyze the following code snippet. Before running it, write down what you predict each console.log will output. This exercise will solidify your mental model of the entire process.

console.log("1:", typeof myFunc);
console.log("2:", typeof myVar);

var myVar = "I am a variable";

function myFunc() {
  return "I am a function";
}

console.log("3:", typeof myFunc);
console.log("4:", typeof myVar);

Think about it...

Here's the analysis:

  1. console.log("1:", typeof myFunc): During the memory creation phase, the function myFunc declaration is hoisted in its entirety. Therefore, myFunc is a function at this point. The output will be "function".
  2. console.log("2:", typeof myVar): The var myVar declaration is also hoisted, but it is initialized with undefined. The typeof an undefined value is "undefined". The output will be "undefined".
  3. console.log("3:", typeof myFunc): Nothing has changed for myFunc. The output is still "function".
  4. console.log("4:", typeof myVar): The code has now executed the line var myVar = "I am a variable". The variable now holds a string. The output will be "string".

Final Output:

1: function
2: undefined
3: function
4: string

Conclusion

In this lesson, we connected the two-phase nature of the execution context to the practical behavior of hoisting. You now have a robust mental model for predicting how different declarations will behave in your code.

Key Takeaways:

  • Hoisting is the effect of the JavaScript engine allocating memory for variables and functions during the memory creation phase, before code execution.
  • Function declarations are fully hoisted, meaning their name and body are available before the execution phase.
  • var declarations are hoisted and initialized with a value of undefined.
  • let and const declarations are hoisted but are not initialized. They are in the Temporal Dead Zone (TDZ) until their declaration is executed, and accessing them in the TDZ throws a ReferenceError.
  • Function expressions and arrow functions follow the hoisting rules of the variable they are assigned to (var, let, or const).

In our next lesson, we will delve deeper into the differences between var, let, and const, focusing on another critical distinction: their scope. We'll explore how let and const introduced block-scoping to JavaScript and why that helps us write more predictable and maintainable code.