12.3-Hoisting-Deep-Dive
6.3 Hoisting Deep Dive
Introduction
Hoisting is JavaScript's behavior of moving declarations to the top of their scope during the creation phase. While it might seem magical, it's actually a predictable result of how execution contexts are created.
What Gets Hoisted?
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HOISTING SUMMARY β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β β
Function declarations - Fully hoisted β
β β
var declarations - Hoisted (as undefined) β
β β οΈ let declarations - Hoisted (but in TDZ) β
β β οΈ const declarations - Hoisted (but in TDZ) β
β β
Class declarations - Hoisted (but in TDZ) β
β β Function expressions - NOT hoisted β
β β Arrow functions - NOT hoisted β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Function Declaration Hoisting
Function declarations are fully hoistedβboth the name and the implementation:
// This works!
sayHello(); // "Hello!"
function sayHello() {
console.log('Hello!');
}
What JavaScript Sees:
// Creation phase
function sayHello() {
console.log('Hello!');
}
// Execution phase
sayHello(); // "Hello!"
Multiple Functions:
// Both are available from the start
first(); // "First!"
second(); // "Second!"
function first() {
console.log('First!');
}
function second() {
console.log('Second!');
}
var Hoisting
Variables declared with var are hoisted, but only the declaration, not the initialization:
console.log(message); // undefined (not an error!)
var message = 'Hello';
console.log(message); // "Hello"
What JavaScript Sees:
// Creation phase
var message; // Declaration hoisted, initialized to undefined
// Execution phase
console.log(message); // undefined
message = 'Hello'; // Assignment happens here
console.log(message); // "Hello"
Common Mistake:
function example() {
console.log(x); // undefined (not an error!)
if (true) {
var x = 10; // Hoisted to function scope!
}
console.log(x); // 10
}
example();
let and const Hoisting (Temporal Dead Zone)
let and const ARE hoisted, but they're in a "Temporal Dead Zone" (TDZ) until the declaration is reached:
// TDZ starts here for 'name'
console.log(name); // ReferenceError: Cannot access 'name' before initialization
let name = 'Alice'; // TDZ ends here
console.log(name); // "Alice"
Temporal Dead Zone Visualized:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β function example() { β
β // βββββββββββββββββββββββββββββββββββββββββββ β
β // β TEMPORAL DEAD ZONE for 'x' β β
β // β β β
β // β console.log(x); // ReferenceError! β β
β // β doSomething(); β β
β // β β β
β // βββββββββββββββββββββββββββββββββββββββββββ β
β let x = 10; // TDZ ends, 'x' is now accessible β
β console.log(x); // 10 β
β } β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Why TDZ Exists:
TDZ prevents the confusing behavior of var:
// With var (confusing)
console.log(x); // undefined - exists but no value
var x = 10;
// With let (clearer error)
console.log(y); // ReferenceError - clearly an error
let y = 10;
Function Expressions Are NOT Hoisted
sayHi(); // TypeError: sayHi is not a function
var sayHi = function () {
console.log('Hi!');
};
What JavaScript Sees:
// Creation phase
var sayHi; // undefined
// Execution phase
sayHi(); // undefined() - TypeError!
sayHi = function () {
console.log('Hi!');
};
With let/const:
sayHi(); // ReferenceError: Cannot access 'sayHi' before initialization
const sayHi = function () {
console.log('Hi!');
};
Arrow Functions Are NOT Hoisted
Same as function expressions:
greet(); // ReferenceError (with const) or TypeError (with var)
const greet = () => {
console.log('Hello!');
};
Hoisting Order of Precedence
When both a function and variable have the same name:
console.log(typeof foo); // "function"
var foo = "I'm a string";
function foo() {
return "I'm a function";
}
console.log(typeof foo); // "string"
Rules:
- β’Function declarations are hoisted first
- β’Variable declarations are ignored if name already exists
- β’Variable assignments happen during execution
Step by Step:
// 1. Creation Phase
function foo() {
return "I'm a function";
} // Function hoisted first
// var foo ignored - 'foo' already exists
// 2. Execution Phase
console.log(typeof foo); // "function"
foo = "I'm a string"; // Assignment overwrites
console.log(typeof foo); // "string"
Class Hoisting
Classes are hoisted but remain in the TDZ:
const instance = new MyClass(); // ReferenceError!
class MyClass {
constructor() {
this.name = 'MyClass';
}
}
Class Expressions:
const instance = new MyClass(); // ReferenceError or TypeError
const MyClass = class {
constructor() {
this.name = 'MyClass';
}
};
Hoisting in Blocks
var Ignores Blocks:
function example() {
if (false) {
var x = 10; // Still hoisted to function scope!
}
console.log(x); // undefined (not an error)
}
example();
let/const Respect Blocks:
function example() {
if (false) {
let x = 10; // Block-scoped
}
console.log(x); // ReferenceError: x is not defined
}
example();
Hoisting in Loops
The Classic var Loop Problem:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3 (not 0, 1, 2!)
Why? var i is hoisted to function scope. There's only ONE i.
The let Fix:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 0, 1, 2
Why? let creates a new i for each iteration.
Best Practices
1. Declare Variables at the Top
// Good: Variables at top
function processUser(user) {
const name = user.name;
const age = user.age;
let status = 'pending';
// ... rest of function
}
// Avoid: Scattered declarations
function processUser(user) {
console.log(name); // undefined (with var) or error (with let)
// ... code ...
var name = user.name;
// ... more code ...
}
2. Use const by Default, let When Needed
const PI = 3.14159; // Never changes
const user = { name: 'A' }; // Reference doesn't change
let count = 0; // Will be reassigned
count++;
3. Avoid var in Modern JavaScript
// Modern JavaScript
const items = [];
let total = 0;
// Avoid
var items = [];
var total = 0;
4. Declare Functions Before Use (for readability)
// Clear and readable
function main() {
doStep1();
doStep2();
doStep3();
}
function doStep1() {
/* ... */
}
function doStep2() {
/* ... */
}
function doStep3() {
/* ... */
}
main();
Summary Table
| Declaration | Hoisted? | Initial Value | Accessible Before Declaration? |
|---|---|---|---|
function declaration | Yes (fully) | The function | Yes |
var | Yes | undefined | Yes (returns undefined) |
let | Yes (TDZ) | Uninitialized | No (ReferenceError) |
const | Yes (TDZ) | Uninitialized | No (ReferenceError) |
class | Yes (TDZ) | Uninitialized | No (ReferenceError) |
| Function expression | No* | N/A | No |
| Arrow function | No* | N/A | No |
*The variable holding the expression is hoisted, but not the function itself.
Common Interview Questions
- β’
What is hoisting?
- β’Moving declarations to the top during the creation phase
- β’
What's the difference between var and let hoisting?
- β’Both are hoisted, but
letis in TDZ until declaration
- β’Both are hoisted, but
- β’
Why does this log undefined?
console.log(x); var x = 5;- β’
var xis hoisted, but assignment isn't
- β’
- β’
What's the Temporal Dead Zone?
- β’The period where let/const exist but can't be accessed
Next Steps
Next, we'll explore the Event Loopβhow JavaScript handles asynchronous operations despite being single-threaded.