Free lesson · JavaScript Basic to Advance
Hoisting Explained
What Is Hoisting?
Hoisting is JavaScript's behavior of processing variable and function declarations before executing your code line by line. It's why you can sometimes call a function before the line that defines it, or read a variable before its declaration — and why doing the same with different keywords behaves completely differently.
console.log(hoistedFunction()); // ✅ works — "Hello"
console.log(hoistedVar); // ✅ works — undefined (not an error!)
// console.log(hoistedLet); // ❌ ReferenceError
function hoistedFunction() {
return "Hello";
}
var hoistedVar = "I'm assigned here";
let hoistedLet = "But you can't access me before this line";
Three different behaviors in the same chunk of code. The function works. The var doesn't throw an error — it just gives you undefined. The let would throw an error if uncommented. Each one is following its own hoisting rules.
I'm Nikhil. I'm a Senior Software Engineer at Google's Distributed Systems team, and I'll be walking you through Topics 3.4 and 3.5 because hoisting is where the JavaScript execution model starts to matter. Hoisting is one of the most-misunderstood concepts in the language, and getting it right is what separates intermediate JavaScript developers from beginners.
The Mental Model: Two Phases
JavaScript doesn't execute your code purely top-to-bottom. The engine processes it in two phases:
Phase 1 — Creation. Before any code runs, JavaScript scans through the current scope and registers all the declarations it finds. Function declarations get their full definitions registered. Variable declarations get their names registered, but not their values.
Phase 2 — Execution. Now JavaScript runs the code line by line, in the order written. When it reaches a variable assignment, it stores the value into the slot that was already registered in Phase 1.
graph TD
A[Function or block begins] --> B[Phase 1: Creation]
B --> C[Scan for declarations]
C --> D[Register all 'function' declarations<br/>with their full definitions]
C --> E[Register 'var' names<br/>initialized to undefined]
C --> F[Register 'let' and 'const' names<br/>in 'uninitialized' state]
D --> G[Phase 2: Execution]
E --> G
F --> G
G --> H[Run code line by line]
H --> I[Assignments store values into<br/>the slots from Phase 1]
style B fill:#7c3aed,color:#fff
style G fill:#10b981,color:#fff
This diagram shows JavaScript's two-phase execution model. Before any code runs, JavaScript performs a creation phase where it scans for all declarations in the current scope. Function declarations are registered with their full definitions. Variable declarations using var are registered with their names and initialized to undefined. Declarations using let and const are registered with their names but left in an uninitialized state. After the creation phase, execution begins, running the code line by line. When the code reaches an assignment, the value is stored into the slot that was prepared during the creation phase.
This two-phase model is what hoisting actually is. There's no magic. The engine isn't physically moving your code around. It's just doing the registration step before the execution step.
The metaphor "hoisting" is misleading because it suggests JavaScript literally lifts declarations to the top of the code. That's not what happens. The declarations are processed during the creation phase, but they stay in their original positions in the source. The behavior is the same as if they had been moved to the top, which is where the metaphor comes from.
Function Declarations: Fully Hoisted
Function declarations — the kind written with the function keyword — are completely processed during the creation phase. By the time execution begins, the function exists in full, with its body ready to run.
sayHello(); // ✅ works — function already exists when this line runs
function sayHello() {
console.log("Hello from a hoisted function");
}
This works because by Phase 2, sayHello is already a complete, callable function. The call on line 1 finds it without any trouble.
This is the behavior that makes function declarations special. You'll see code in real codebases where helper functions are defined at the bottom of a file and called from the top. It works because of hoisting.
var: Hoisted but Not Initialized
Variables declared with var are registered during the creation phase, but their values are not. Each var gets registered as undefined until the line of code that assigns it is reached.
console.log(myVar); // undefined (not an error)
var myVar = "Now I have a value";
console.log(myVar); // "Now I have a value"
That first console.log doesn't throw a ReferenceError, because myVar has been registered. It just hasn't been assigned yet, so its current value is undefined.
This is the behavior that confuses developers most. Reading from a var before its declaration line works fine — it just gives you undefined. There's no error, no warning. If you expected a different value, you might not notice the bug until much later.
Here's a longer example showing what's actually happening:
function demonstrate() {
// What you wrote:
console.log(name);
var name = "Olena";
console.log(name);
}
demonstrate();
// How JavaScript effectively processes it (conceptually):
//
// function demonstrate() {
// var name; // registered during creation phase
// console.log(name); // undefined — name registered but not assigned
// name = "Olena"; // assignment runs here in execution phase
// console.log(name); // "Olena" — now it has a value
// }
The commented section is not how the code actually looks after parsing — JavaScript doesn't rewrite your file. But it shows the behavior you can expect. Reading from name before the assignment line gives you undefined, not an error.
Interactive animation: Why the classic snippet prints undefined - the var is registered in the creation phase, assigned only when its line runs.
let and const: Hoisted but Not Accessible
let and const are also hoisted, but with a critical difference: accessing them before the declaration line throws a ReferenceError instead of giving you undefined.
// console.log(myLet); // ❌ ReferenceError: Cannot access 'myLet' before initialization
let myLet = "Now accessible";
console.log(myLet);
The variable is hoisted — JavaScript knows it exists in this scope — but it's in an "uninitialized" state until the declaration line is reached. Reading from it during that period throws an error.
This period between the start of the scope and the declaration line, where the variable exists but can't be accessed, is called the Temporal Dead Zone. It has its own Topic (3.5) because the behavior is significant enough to warrant separate treatment.
The design intent here is to prevent the kind of silent bug var allows. With var, reading too early just gives you undefined with no warning. With let and const, reading too early throws a loud, immediate error. The error is the feature.
Function Expressions: Different Hoisting
There's one more case that catches developers off guard. Function declarations are fully hoisted, but function expressions are not.
// Function declaration — fully hoisted
declaredFn(); // ✅ works
function declaredFn() {
console.log("declared");
}
// Function expression assigned to var — partially hoisted
// expressionFn(); // ❌ TypeError: expressionFn is not a function
var expressionFn = function() {
console.log("expression");
};
expressionFn(); // ✅ works after this line
A function expression is a function value assigned to a variable. The variable follows the rules of its declaration keyword (var, let, or const). When expressionFn is declared with var, the variable name is hoisted, but its value is undefined until the assignment runs. Trying to call undefined() throws a TypeError.
This distinction matters because modern JavaScript style guides often prefer function expressions assigned to const for various reasons. When you adopt that style, you lose the "call functions before defining them" flexibility — but you gain other things in exchange, like consistent block-scoping and immutable function bindings.
Interactive animation: The same function called before its own line - as a declaration it works, held in a const it throws.
The Real Mechanism Underneath
For senior engineers and interview prep, you should understand the actual mechanism, not just the behavior.
JavaScript runs in execution contexts — Module 6 covers this in depth, but the short version is: every function call creates an execution context, and the global code itself runs inside its own execution context. Each execution context has two phases: a creation phase and an execution phase.
During the creation phase, the engine processes the variable environment and lexical environment of that context. Function declarations get added with full definitions. var declarations get added with undefined values. let and const declarations get added in an uninitialized state.
The phrase "hoisting" is a teaching shortcut for this process. The mechanism is precisely defined in the ECMAScript specification, and it's worth knowing the real terms if you're interviewing at senior levels.
Interactive animation: What each kind of declaration holds before its own line runs, and what happens if you read it early.
Common Pitfalls
Warning: Three hoisting traps that hurt real codebases.
Reading
varvariables before their declaration. This doesn't throw an error — you getundefined. If your code logic depended on the variable having a specific value, you'll get silently wrong behavior. Hard bug to track down.Calling function expressions before their assignment. This throws a TypeError ("X is not a function"), which is at least loud. Confusing because beginners assume "all functions are hoisted" — but only function declarations are fully hoisted.
Assuming hoisting moves code physically. It doesn't. The declarations are processed during the creation phase, but the source code stays where you wrote it. Don't try to reason about hoisting by imagining lines of code physically jumping around — reason about it by remembering the two phases.
Why This Matters in Interviews
Interview: Hoisting questions show up in nearly every JavaScript interview. The classic forms:
- Conceptual: "What is hoisting?" — Answer in terms of the two-phase model, not just "JavaScript moves declarations to the top."
- Output prediction: Code with declarations after their first use, asking what gets logged.
- Comparison: "What's the difference between hoisting
var,let,const, and function declarations?" — All four are hoisted; only function declarations andvarare usable before their declaration line.Topic 3.6 — Interview Lens: Variables, Scope, and Hoisting — covers the full interview-prep treatment, including the puzzles interviewers use to test whether you understand the mechanism or just the buzzword.
Try It
Exercise: Predict the output of this code. Then run it.
console.log(typeof a); console.log(typeof b); console.log(typeof c); console.log(typeof greet); var a = 1; let b = 2; const c = 3; function greet() { return "hello"; }Two of the four
typeofcalls work cleanly. One returns something specific even though the variable hasn't been assigned. One throws an error. Identify each, and trace through the rule that produces each outcome. The pattern reveals exactly how JavaScript handles hoisting across the four declaration types.