Course: JavaScript Intermediate
Lesson 2: Hoisting Basics
Level
Intermediate
Estimated Reading Time
10–15 minutes
Goal
By the end of this lesson, the learner will be able to:
- Explain what hoisting is and how it affects
vardeclarations and function declarations - Describe the temporal dead zone for
letandconst - Avoid bugs caused by accessing variables or functions before they are declared
Frontend Development Context
Hoisting is one of JavaScript's more surprising behaviors, and it catches developers off guard most often when reading or debugging older code. When a script loads, JavaScript processes declarations before running any code — so function declarations and var variables are available earlier than you might expect. This can make code work in ways that seem out of order.
In modern frontend development, you are more likely to encounter hoisting as a source of bugs than as a feature to rely on. Understanding it helps you read legacy code confidently, avoid writing code that depends on it accidentally, and understand error messages like "Cannot access before initialization" when they appear.
Explanation
Hoisting refers to the behavior where JavaScript moves certain declarations to the top of their scope during the compilation phase, before any code executes. Only the declarations are hoisted — not the initializations (the assigned values).
For var, the declaration is hoisted and initialized to undefined. This means you can reference a var variable before the line where you assign it, and JavaScript will not throw an error — it will just return undefined. This is a common source of confusion because the code appears to work but produces wrong values.
Function declarations are fully hoisted — both the declaration and the body. This means you can call a function before the line where it is defined and it will work correctly. This is why some developers write their function calls at the top of a file and function definitions below.
let and const are hoisted differently. Their declarations are recognized by JavaScript during the compilation phase, but they are not initialized until the code reaches the declaration line. The period between the start of the scope and the declaration line is called the temporal dead zone (TDZ). Accessing a let or const variable in the TDZ throws a ReferenceError.
Understanding the difference between var (hoisted and initialized to undefined), function declarations (fully hoisted), and let/const (hoisted but in TDZ) gives you a clear mental model of how JavaScript reads your code before running it.
Code Example
This example shows how hoisting affects var, function declarations, and let in different ways.
console.log(score); // undefined — var is hoisted, but not yet assigned
var score = 10;
console.log(score); // 10
// Function declarations are fully hoisted
greet("Alex"); // works — function body is available before its definition
function greet(name) {
console.log(`Hello, ${name}!`);
}
// let is in the temporal dead zone before its declaration
try {
console.log(level); // ReferenceError
} catch (e) {
console.log(e.message); // Cannot access 'level' before initialization
}
const level = "Intermediate";
Code Explanation
The first two lines demonstrate var hoisting. JavaScript sees var score during compilation and initializes it to undefined. When console.log(score) runs before the assignment, it gets undefined rather than an error. After the assignment line, score correctly holds 10.
greet("Alex") is called before the function definition appears in the file, and it works. Function declarations are fully hoisted — the entire function body is available from the start of the scope. This is a legitimate JavaScript feature, though relying on it heavily can make large files harder to read top to bottom.
The try/catch block around console.log(level) demonstrates the temporal dead zone. const level is recognized during compilation but not initialized yet. Accessing it before the declaration line throws a ReferenceError. The try/catch captures it so the rest of the script continues running.
Pattern Highlights
✅ Positive Pattern: Declare variables at the top of their scope and define functions before you call them. This makes hoisting irrelevant in practice — your code reads in the same order it executes.
⚠️ Neutral Note: Function declarations are fully hoisted, so calling them before their definition is technically valid. In small files this may be fine, but in larger codebases it can make code harder to follow.
❌ Negative Pattern: Do not rely on
varhoisting to produceundefinedas a way to check whether something has been set. This is a fragile pattern thatletandconsteliminate entirely.
Common Mistakes
Mistake 1: Expecting a var variable to hold its assigned value before assignment
Developers sometimes assume that if the variable is declared in the file, it already holds its value.
console.log(username); // undefined, not "Alice"
var username = "Alice";
The declaration is hoisted but the assignment is not. The variable exists but holds undefined until the assignment line executes.
Mistake 2: Treating function expressions like function declarations
Function expressions assigned to variables do not get fully hoisted.
sayHi(); // TypeError: sayHi is not a function
var sayHi = function () {
console.log("Hi!");
};
var sayHi is hoisted and initialized to undefined. Calling undefined as a function throws a TypeError. Only function declarations using the function keyword at the statement level are fully hoisted.
Mistake 3: Accessing let or const before their declaration line
Expecting let to behave like var when accessed before its declaration.
console.log(theme); // ReferenceError: Cannot access 'theme' before initialization
let theme = "dark";
Unlike var, let and const do not initialize to undefined when hoisted. They remain in the temporal dead zone until the declaration line, and accessing them there throws a ReferenceError.
Quick Recap
vardeclarations are hoisted and initialized toundefined— accessing them before assignment givesundefined, not an error- Function declarations are fully hoisted — you can call them before their definition in the file
letandconstare hoisted but not initialized — accessing them before their declaration throws a ReferenceError (temporal dead zone)- The safest practice is to declare everything before you use it, making hoisting behavior a non-issue