JavaScript Hoisting

In the previous lesson, we learned where variables live and which parts of the code can see them. Now let’s look at hoisting, which explains why some declarations seem to work even before the line that creates them.

🎈 What is Hoisting?

Hoisting is the way JavaScript moves declarations to the top of their scope before the code runs. The engine reads through your code once to find every declaration, then runs the code from the top. Because of this first pass, a name can already exist before the line where you wrote it.

How a name behaves before its declaration depends on how it was declared.

Declaration Hoisted? Usable before its line? Value before its line
Function declaration Yes, fully Yes, you can call it The full function
var Yes, name only Yes, but with no real value undefined
let and const Yes, name only No, it throws an error Temporal dead zone

πŸ“ž Function Declarations Are Fully Hoisted

A function declaration is moved to the top of its scope with its whole body. That means you can call the function before the line where it appears.

hoisting.js
greet(); // βœ… "Hello!" β€” works even though it is called first
function greet() {
console.log("Hello!");
}

Let’s walk through how the engine handles this:

  • Line 1 calls greet() even though the function is written further down.
  • The function greet() declaration on line 3 is found during the first pass and lifted to the top of the scope with its full body.
  • By the time line 1 actually runs, greet already points to the complete function, so the call prints "Hello!".

The engine sees the entire greet function during its first pass, so by the time the code runs the function is ready to call.

πŸ“¦ var Is Hoisted as undefined

A var declaration is also hoisted, but only the name moves to the top. The value stays on its original line. So before that line, the variable exists but holds undefined.

hoisting.js
console.log(score); // undefined β€” the name exists, the value does not yet
var score = 10;
console.log(score); // 10

Here is what happens line by line:

  • The name score is hoisted to the top, but its assignment = 10 stays on line 2.
  • Line 1 reads score before that assignment runs, so it prints undefined.
  • Line 2 runs the assignment, giving score the value 10.
  • Line 3 reads score after the assignment, so it prints 10.

JavaScript reads this as if the var score part were lifted to the top, while the assignment = 10 stays where you wrote it.

undefined is not an error

Reading a var before its assignment does not crash. It quietly returns undefined, which can hide bugs because the code keeps running with the wrong value.

🚫 let and const Live in the Temporal Dead Zone

A let or const declaration is hoisted too, but you cannot touch it before its line. The space between the top of the scope and the declaration is called the temporal dead zone. Using the name there throws a ReferenceError.

hoisting.js
console.log(total); // ❌ ReferenceError: Cannot access 'total' before initialization
let total = 5;

Let’s trace what the engine does:

  • The name total is hoisted, but it stays uninitialized until line 2 runs.
  • Line 1 reaches total while it is still in the temporal dead zone, so the engine throws a ReferenceError instead of returning a value.
  • The program stops at that error and never reaches the declaration on line 2.

This is helpful. Instead of silently giving you undefined like var, the engine stops and tells you that you used the variable too early.

hoisting.js
// βœ… Declare first, then use
const total = 5;
console.log(total); // 5

Here is why this version works:

  • The const total = 5 declaration runs first, which initializes total and lifts it out of the temporal dead zone.
  • Line 3 then reads total after it holds a value, so it prints 5.

⏳ Function Expressions Follow the Variable Rules

A function expression stores a function inside a variable. Because the variable is what gets hoisted, the same rules apply: with const or let you cannot call it before its line.

hoisting.js
sayHi(); // ❌ ReferenceError β€” sayHi is in the temporal dead zone
const sayHi = function () {
console.log("Hi!");
};

Let’s break down why this fails:

  • sayHi is declared with const, so it follows the variable rules, not the function rules.
  • The name sayHi is hoisted but left in the temporal dead zone until its declaration on line 3 runs.
  • Line 1 tries to call sayHi() while it is still uninitialized, so the engine throws a ReferenceError before the function is ever assigned.

Only function declarations are fully hoisted. A function stored in a variable is not.

⚠️ Common Mistakes to Avoid

Mistake Problem Solution
Relying on var hoisting Reading a var early gives undefined, hiding bugs Declare the variable before you use it
Expecting let and const to be undefined They throw a ReferenceError, not undefined Always declare them before any use
Calling a function expression early The variable is not ready, so the call fails Define the expression before calling it

The safe rule

Declare every variable and function before you use it, and prefer const and let over var. Then hoisting never surprises you.

πŸ”§ Try It Yourself!

  1. Call a function declaration before it appears in the file and confirm it still runs.
  2. Log a var before its assignment and see that you get undefined.
  3. Log a let before its declaration and read the ReferenceError it throws.
  4. Store a function in a const and try calling it before its line to see the temporal dead zone.

🧩 What You’ve Learned

  • βœ… Hoisting moves declarations to the top of their scope before the code runs
  • βœ… Function declarations are fully hoisted, so you can call them early
  • βœ… var is hoisted but holds undefined until its assignment runs
  • βœ… let and const are hoisted but unusable in the temporal dead zone
  • βœ… Function expressions follow the variable rules, not the function rules
  • βœ… Declaring before use and preferring const and let avoids surprises

πŸš€ What’s Next?

Now that you understand how the engine prepares your declarations, let’s see how it runs your code over time. Let’s continue to Event Loop.

Share & Connect