Q&A
What is JavaScript hoisting?
Overview
Before executing code, the JavaScript engine scans each scope and "hoists" declarations to the top - but how depends on the declaration type. The variable/function name becomes known early, but its value may not be assigned until the original line runs. Misunderstanding this leads to classic bugs.
graph TD
A[Declaration type] --> B[function declaration: fully hoisted, usable before definition]
A --> C[var: hoisted, initialized to undefined]
A --> D[let / const: hoisted but uninitialized → Temporal Dead Zone]
D --> E[Access before declaration → ReferenceError]
style D fill:#1e1b4b,color:#fff
Function Declarations: Fully Hoisted
You can call a function declaration before it appears in the code:
greet(); // "Hello!" - works!
function greet() {
console.log("Hello!");
}
The entire function definition is hoisted, so it's available throughout its scope.
var: Hoisted but undefined
A var declaration is hoisted, but its assignment stays where it is. Before the assignment line, the variable exists with value undefined:
console.log(x); // undefined (not an error - x is hoisted, value not yet set)
var x = 5;
console.log(x); // 5
// What the engine effectively does:
// var x; // hoisted declaration, x = undefined
// console.log(x); // undefined
// x = 5; // assignment stays here
let and const: Temporal Dead Zone (TDZ)
let and const are also hoisted, but not initialized - accessing them before their declaration throws a ReferenceError. The region from the start of the scope to the declaration is the Temporal Dead Zone:
console.log(y); // ❌ ReferenceError: Cannot access 'y' before initialization
let y = 10;
This is intentional - it catches use-before-declaration bugs that var silently hides (returning undefined).
Function Expressions Are NOT Fully Hoisted
Only function declarations are fully hoisted. A function assigned to a variable follows that variable's hoisting rules:
greet(); // ❌ TypeError: greet is not a function
var greet = function () { // var hoisted as undefined; assignment not yet run
console.log("Hi");
};
// With const/let it would be a ReferenceError (TDZ) instead.
Summary Table
| Declaration | Hoisted? | Usable before line? |
|---|---|---|
function foo() {} |
Yes (whole function) | Yes |
var x |
Yes (as undefined) |
Yes, but value is undefined |
let / const |
Yes (uninitialized) | No - ReferenceError (TDZ) |
| function expression / arrow | Follows its variable's rule | No |
class |
Hoisted but in TDZ | No - ReferenceError |
Best Practices
- Declare variables at the top of their scope to make hoisting irrelevant.
- Prefer
let/constovervar- the TDZ turns silentundefinedbugs into clear errors, and block scoping is more predictable. - Define functions before using them for readability, even though declarations are hoisted.
Why This Matters in Interviews
Hoisting is a top JavaScript interview topic because it explains many surprising results. Be precise: declarations are hoisted, initializations are not; var → undefined before assignment; let/const → TDZ → ReferenceError; function declarations are fully hoisted but function expressions/arrows are not (they follow their variable). Stating why let/const's TDZ is a feature (catches bugs) shows real understanding rather than memorization.
Common Pitfalls
- Expecting
let/constto beundefinedbefore declaration (they throw instead). - Assuming function expressions are callable before their line (only declarations are).
- Relying on
varhoisting, masking use-before-assignment bugs. - Forgetting
classdeclarations are also in a TDZ.
References
- MDN Hoisting: https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
- MDN
let(Temporal Dead Zone): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let