JavaScript Hoisting
Table of Contents + β
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.
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,
greetalready 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.
console.log(score); // undefined β the name exists, the value does not yetvar score = 10;console.log(score); // 10Here is what happens line by line:
- The name
scoreis hoisted to the top, but its assignment= 10stays on line 2. - Line 1 reads
scorebefore that assignment runs, so it printsundefined. - Line 2 runs the assignment, giving
scorethe value10. - Line 3 reads
scoreafter the assignment, so it prints10.
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.
console.log(total); // β ReferenceError: Cannot access 'total' before initializationlet total = 5;Letβs trace what the engine does:
- The name
totalis hoisted, but it stays uninitialized until line 2 runs. - Line 1 reaches
totalwhile it is still in the temporal dead zone, so the engine throws aReferenceErrorinstead 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.
// β
Declare first, then useconst total = 5;console.log(total); // 5Here is why this version works:
- The
const total = 5declaration runs first, which initializestotaland lifts it out of the temporal dead zone. - Line 3 then reads
totalafter it holds a value, so it prints5.
β³ 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.
sayHi(); // β ReferenceError β sayHi is in the temporal dead zone
const sayHi = function () { console.log("Hi!");};Letβs break down why this fails:
sayHiis declared withconst, so it follows the variable rules, not the function rules.- The name
sayHiis 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 aReferenceErrorbefore 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!
- Call a function declaration before it appears in the file and confirm it still runs.
- Log a
varbefore its assignment and see that you getundefined. - Log a
letbefore its declaration and read theReferenceErrorit throws. - Store a function in a
constand 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
- β
varis hoisted but holdsundefineduntil its assignment runs - β
letandconstare hoisted but unusable in the temporal dead zone - β Function expressions follow the variable rules, not the function rules
- β
Declaring before use and preferring
constandletavoids 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.