Hoisting & Temporal Dead Zone

1. Variable Hoisting
Hoisting is JavaScript’s default behavior of moving variable and function declarations to the top of their containing scope (global or function scope) during the compilation phase, before the code is executed. However, only declarations are hoisted, not initializations or assignments.
a) var
Hoisting
Variables declared with
var
are hoisted to the top of their scope and initialized withundefined
.You can access a
var
variable before its declaration, but it will returnundefined
because the initialization (assignment) stays in place.
Example:
console.log(x); // Output: undefined
var x = 5;
console.log(x); // Output: 5
How it works internally: JavaScript moves the declaration to the top, so the code is interpreted as:
var x; // Declaration hoisted, initialized to undefined
console.log(x); // undefined
x = 5; // Assignment stays in place
console.log(x); // 5
b) let
and const
Hoisting
Variables declared with
let
andconst
are also hoisted, but they are not initialized until their declaration line is reached in the code.This creates a Temporal Dead Zone (TDZ) (explained below) where accessing the variable before its declaration throws a
ReferenceError
.Unlike
var
,let
andconst
are block-scoped, not function-scoped.
Example:
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
Key Difference:
var
: Hoisted and initialized withundefined
.let
/const
: Hoisted but not initialized, leading to TDZ.
2. Function Hoisting
Function declarations are fully hoisted, meaning both the declaration and the function body are moved to the top of the scope. This allows you to call a function before its declaration in the code.
a) Function Declarations
sayHello(); // Output: "Hello!"
function sayHello() {
console.log("Hello!");
}
How it works:
- The entire function
sayHello
is hoisted, so it’s available anywhere in its scope, even before the declaration.
b) Function Expressions
Function expressions (where a function is assigned to a variable) are treated differently based on the variable’s declaration type (
var
,let
, orconst
).The variable declaration is hoisted, but the function assignment is not.
Example with var
:
console.log(myFunc); // Output: undefined
myFunc(); // TypeError: myFunc is not a function
var myFunc = function() {
console.log("Hi!");
};
How it works:
The
var myFunc
declaration is hoisted and initialized toundefined
.The assignment
myFunc = function() {...}
stays in place.Trying to call
myFunc()
before the assignment throws aTypeError
becauseundefined
is not a function.
Example with let
/const
:
console.log(myFunc); // ReferenceError: Cannot access 'myFunc' before initialization
let myFunc = function() {
console.log("Hi!");
};
- The
let myFunc
declaration is hoisted but not initialized (TDZ applies).
3. Temporal Dead Zone (TDZ)
The Temporal Dead Zone is the period between the start of a block scope and the point where a let
or const
variable is declared. During this time, accessing the variable results in a ReferenceError
.
Why TDZ Exists
let
andconst
were introduced in ES6 to provide block-scoping and prevent unsafe access to variables before they are initialized.The TDZ enforces that you cannot use a variable until its declaration is reached, making code behavior more predictable.
Example:
{
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 20;
}
TDZ Explanation:
The variable
a
is hoisted to the top of the block scope but remains uninitialized until thelet a = 20
line is executed.Any attempt to access
a
before this line throws aReferenceError
.
Key Points about TDZ:
Applies to
let
andconst
variables (notvar
).Starts at the beginning of the block scope and ends when the variable is declared.
Even checking
typeof
on a TDZ variable throws aReferenceError
:console.log(typeof x); // ReferenceError: Cannot access 'x' before initialization let x = 10;
Exercises: Debug 5 Hoisting-Related Code Snippets
Let’s debug five code snippets to understand hoisting and TDZ in action. For each, I’ll explain the issue, predict the output, and provide a fix (if applicable).
Snippet 1
console.log(a);
var a = 10;
console.log(a);
Expected Output:
undefined
10
Explanation:
var a
is hoisted to the top and initialized withundefined
.The first
console.log(a)
outputsundefined
.The assignment
a = 10
happens, so the secondconsole.log(a)
outputs10
.No error because
var
variables are initialized during hoisting.
Fix (if you want to avoid accessing before declaration):
Move the declaration before the first
console.log
:var a = 10; console.log(a); // 10 console.log(a); // 10
Snippet 2
console.log(b);
let b = 20;
Expected Output:
ReferenceError: Cannot access 'b' before initialization
Explanation:
let b
is hoisted to the top of the block scope but is not initialized (TDZ).Accessing
b
before its declaration throws aReferenceError
due to the TDZ.
Fix:
Declare and initialize
b
before accessing it:let b = 20; console.log(b); // 20
Snippet 3
greet(); // Works fine
function greet() {
console.log("Hello, World!");
}
Expected Output:
Hello, World!
Explanation:
Function declarations are fully hoisted, including their body.
The
greet
function is available before its declaration, so callinggreet()
works.No issue to fix here; this is expected behavior.
Snippet 4
console.log(myFunc);
var myFunc = function() {
console.log("Function Expression");
};
myFunc();
Expected Output:
undefined
Function Expression
Explanation:
var myFunc
is hoisted and initialized toundefined
.The first
console.log(myFunc)
outputsundefined
.The assignment
myFunc = function() {...}
happens, somyFunc()
calls the function and outputs"Function Expression"
.If you tried
myFunc()
before the assignment, it would throw aTypeError: myFunc is not a function
.
Fix (to avoid accessing before assignment):
Declare and assign the function expression before logging:
var myFunc = function() { console.log("Function Expression"); }; console.log(myFunc); // [Function: myFunc] myFunc(); // Function Expression
Snippet 5
{
console.log(c);
const c = 30;
console.log(c);
}
Expected Output:
ReferenceError: Cannot access 'c' before initialization
Explanation:
const c
is hoisted to the top of the block scope but is in the TDZ until its declaration.The first
console.log(c)
throws aReferenceError
becausec
is accessed before initialization.The second
console.log(c)
is never reached due to the error.
Fix:
Declare and initialize
const c
before accessing it:{ const c = 30; console.log(c); // 30 console.log(c); // 30 }
Key Takeaways
Hoisting:
var
: Hoisted and initialized toundefined
.let
/const
: Hoisted but not initialized (leads to TDZ).Function declarations: Fully hoisted (declaration + body).
Function expressions: Only the variable declaration is hoisted, not the assignment.
Temporal Dead Zone:
Applies to
let
andconst
.From the start of the block scope to the declaration line, accessing the variable throws a
ReferenceError
.
Best Practices:
Declare variables (
var
,let
,const
) at the top of their scope to avoid hoisting-related surprises.Use
let
orconst
overvar
for block-scoping and to leverage TDZ for safer code.Avoid calling functions or accessing variables before their declaration/assignment.
Additional Notes
Why TDZ is useful: It catches bugs early by preventing access to uninitialized variables, unlike
var
which silently returnsundefined
.Hoisting in strict mode: Using
"use strict"
doesn’t change hoisting behavior but can prevent other sloppy practices (e.g., using undeclared variables).Real-world debugging: Hoisting issues often arise in large codebases where variable/function usage isn’t clearly ordered. Always initialize variables before use.
Subscribe to my newsletter
Read articles from Omkar Patil directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
