Understanding JavaScript Hoisting for Beginners
Hoisting is JavaScript's behavior of moving declarations to the top of their scope. Learn how it works for var, let, const, and function declarations -- and the bugs it can cause.
Hoisting is JavaScript's behavior of making declarations available before the line where they appear in the code. The engine processes declarations first, then executes the code. The result acts as if declarations were physically moved to the top of their scope.
console.log(greet("Alex")); // "Hello, Alex!"
function greet(name) {
return `Hello, ${name}!`;
}The function is called before its definition line, but it works. That is function hoisting. The entire function -- name and body -- is available from the top of the scope.
How Hoisting Actually Works
JavaScript runs code in two phases:
- Creation phase: the engine scans for declarations and registers them in memory.
- Execution phase: the engine runs the code line by line.
Hoisting is the visible result of phase 1. Declarations are registered before any code runs, so they seem to be "moved up."
Function Declarations: Full Hoisting
Function declarations are hoisted completely. Both the name and the body are available before the definition:
console.log(double(4)); // 8
function double(n) {
return n * 2;
}This is the only declaration type where you can use the value before the line. It is why many developers order their code with helper functions at the bottom and the main logic at the top.
var: Hoisted but undefined
var declarations are hoisted, but only the declaration, not the assignment:
console.log(name); // undefined (not ReferenceError!)
var name = "Alex";
console.log(name); // "Alex"Mentally, it helps to expand what the engine actually does during the creation phase. What JavaScript effectively sees is closer to this, with the declaration split apart from the assignment:
var name; // Hoisted declaration, initialized to undefined
console.log(name); // undefined
name = "Alex"; // Assignment stays at the original line
console.log(name); // "Alex"The variable exists from the top but holds undefined until the assignment line runs. This is why var can cause subtle bugs, accessing a var before its assignment does not throw an error, it silently returns undefined.
let and const: Hoisted but in the Temporal Dead Zone
let and const are hoisted but placed in the temporal dead zone (TDZ). You cannot access them before the declaration line at all:
console.log(age); // ReferenceError: Cannot access 'age' before initialization
let age = 25;The variable exists in the scope from the top, but JavaScript enforces a "no touching" rule until the declaration line executes. This is safer than var's silent undefined behavior, you get an immediate, clear error instead.
// Temporal dead zone in action
const x = 1;
{
console.log(x); // ReferenceError -- TDZ for the inner x
const x = 2; // This declaration "taints" the whole block
}Even though there is an x in the outer scope, the inner const x declaration applies to the entire block. The console.log line is in the TDZ of the inner x, so it throws.
Function Expressions and Arrow Functions: Not Hoisted
Only the variable declaration is hoisted for function expressions and arrow functions. The function itself is assigned at runtime:
console.log(typeof add); // "undefined" (with var) or ReferenceError (with let/const)
var add = function(a, b) {
return a + b;
};
// Or with let/const:
console.log(typeof subtract); // ReferenceError
const subtract = (a, b) => a - b;If you use var, the variable exists but is undefined, calling it throws a TypeError saying it is not a function. If you use let or const, accessing before the line throws a ReferenceError instead.
Hoisting Comparison
| Declaration | Hoisted? | Usable Before Line? | Value Before Line |
|---|---|---|---|
function name() {} | Yes, fully | Yes | The function |
var x = value | Declaration only | Yes, but risky | undefined |
let x = value | Yes, but TDZ | No | ReferenceError |
const x = value | Yes, but TDZ | No | ReferenceError |
const fn = function() {} | Variable only | No | ReferenceError |
const fn = () => {} | Variable only | No | ReferenceError |
class Name {} | Yes, but TDZ | No | ReferenceError |
Common Hoisting Bug: var in Loops
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 3, 3, 3 (not 0, 1, 2)Because var is function-scoped, there is only one i variable. By the time the callbacks run, the loop has finished and i is 3. Using let fixes this because let creates a new binding per iteration:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 0, 1, 2Best Practices
- Declare variables at the top of their scope. This makes hoisting behavior obvious.
- Use let and const instead of var. The TDZ catches accidental use-before-declaration.
- Declare functions before calling them when possible. Even though hoisting allows the reverse, top-down ordering is easier to read.
- Use function expressions or arrows for conditional or one-off functions.
For more on variable declarations, see JavaScript Function Scope Local vs Global Scope. For how hoisting interacts with function forms, see JavaScript Function Expressions vs Declarations.
Rune AI
Key Insights
- Hoisting means declarations are processed before code execution, as if moved to the top of their scope.
- Function declarations are fully hoisted -- you can call them before their definition line.
- var variables are hoisted but initialized to undefined until the assignment line runs.
- let and const are hoisted but inaccessible before their declaration (temporal dead zone).
- Function expressions and arrow functions are not hoisted -- only their variable declaration is.
Frequently Asked Questions
Are let and const hoisted?
Is hoisting a real process in the JavaScript engine?
Why does JavaScript have hoisting?
Conclusion
Hoisting is JavaScript's two-pass approach to running code: first it registers declarations, then it executes. Function declarations are fully hoisted and ready to call. var variables are hoisted but start as undefined. let and const are hoisted but locked in the temporal dead zone until their declaration line. Understanding these three behaviors prevents some of JavaScript's most confusing bugs.
More in this topic
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.
How to Loop Through Arrays Using JS for Loops Guide
Looping through arrays is the most practical use of JavaScript for loops. Learn how to access each element by index, avoid off-by-one errors, and choose between for, for...of, and forEach.