Sitemap
Jashandeep Kaur

Jashandeep Kaur

Senior Frontend Engineer | React & TypeScript | Building scalable web apps. Sharing insights on frontend, architecture, performance, and JavaScript.

JavaScript Hoisting Explained: var, let, const & Temporal Dead Zone

8 min readSep 10, 2026

--

JavaScript Hoisting Explained: var, let, const & Temporal Dead Zone

Press enter or click to view image in full size
JavaScript Hoisting Explained: var, let, const & Temporal Dead Zone

Consider this JavaScript:

console.log(a);
var a = 10;

Output:

undefined

But if we replace var with let:

console.log(a);
let a = 10;

we get:

ReferenceError: Cannot access 'a' before initialization

And const behaves similarly:

console.log(a);
const a = 10;
ReferenceError: Cannot access 'a' before initialization

Why? You may have heard:

“var is hoisted, but let and const are not."

That’s a common shortcut, but it’s not technically accurate.

var, let, and const declarations are all processed before normal statement execution.

The important difference is how their bindings are initialized.

To understand this properly, we need to connect:

Execution Context
↓
Setup / Creation
↓
Variable Bindings
↓
var / let / const
↓
Hoisting
↓
Temporal Dead Zone

1. First: var, let, and const

JavaScript provides three common ways to declare variables:

var name = "Jashan";
let age = 26;
const role = "Frontend Engineer";

At a high level:

var
├── Function-scoped
├── Reassignment ✅
├── Redeclaration ✅
├── Starts as undefined
└── TDZ ❌

let
├── Block-scoped
├── Reassignment ✅
├── Redeclaration ❌
├── Starts uninitialized
└── TDZ ✅

const
├── Block-scoped
├── Reassignment ❌
├── Redeclaration ❌
├── Starts uninitialized
└── TDZ ✅

We’ll understand why these differences exist rather than simply memorizing the table.

2. What Is Hoisting?

A common explanation of hoisting is:

“JavaScript moves declarations to the top.”

For example, developers sometimes imagine:

console.log(a);
var a = 10;

becoming:

var a;
console.log(a);
a = 10;

This produces the correct result for this simple example, but JavaScript does not literally rearrange your source code.

A better definition is:

Hoisting describes the behavior where declarations are processed and their bindings are established before normal execution reaches their declaration line.

Remember from the blog that we used this simplified model:

Execution Context
↓
Setup / Creation
↓
Code Execution

Hoisting becomes much easier to understand when connected to that setup.

3. What Happens with var?

Consider:

console.log(a);
var a = 10;
console.log(a);

During setup, JavaScript establishes the var binding and initializes it with:

undefined

Simplified:

SETUP
a → undefined

Then normal execution begins.

First line

console.log(a);

At this point:

a → undefined

So:

undefined

is printed.

Next

var a = 10;

During execution, the assignment happens:

a → 10

Then:

console.log(a);

prints:

10

Complete flow:

SETUP
a → undefined
↓
EXECUTION
console.log(a) → undefined
a = 10
console.log(a) → 10

This is the behavior developers commonly describe as:

“var is hoisted."

4. Declaration vs Assignment

This distinction is very important.

When we write:

var a = 10;

there are conceptually two things involved:

Declaration
var a
Assignment
a = 10

The declaration is processed as part of environment setup.

The assignment occurs when execution reaches that statement.

So:

console.log(a);
var a = 10;

does not mean:

a → 10

before the first console.log.

It means, conceptually:

Before normal execution:
a → undefined
During execution:
a → 10

5. What Happens with let?

Now consider:

console.log(a);
let a = 10;

A common explanation says:

“let is not hoisted."

But that’s misleading.

The let binding is also established before normal execution reaches its declaration.

The difference is that it is not initialized with undefined like var.

Conceptually:

SETUP
var a → initialized with undefined
let b → uninitialized

For:

console.log(a);
let a = 10;

before execution reaches:

let a = 10;

the binding exists, but it is uninitialized.

Therefore, trying to access it throws:

ReferenceError:
Cannot access 'a' before initialization

6. Temporal Dead Zone (TDZ)

The period between entering the relevant scope and the point where a let or const binding is initialized is called the:

Temporal Dead Zone (TDZ)

Example:

{
// TDZ for user starts
console.log(user); // ❌ ReferenceError
let user = "Jashan";
// TDZ for user has ended
console.log(user); // ✅ Jashan
}

Visualize it as:

Enter Scope
│
▼
┌──────────────────────┐
│ Temporal Dead Zone │
│ │
│ user exists as a │
│ binding, but cannot │
│ be accessed yet │
└──────────────────────┘
│
▼
let user = "Jashan"
│
│ initialized
▼
user can be accessed ✅

Simple definition

TDZ is the period where a let or const binding exists but cannot be accessed because it has not yet been initialized.

7. Why Does TDZ Exist?

Consider:

console.log(user);
let user = "Jashan";

If let behaved exactly like var, the first line might silently give:

undefined

Instead, JavaScript throws an error.

This helps catch code that accesses a variable before the point where the programmer intended it to become usable.

Compare:

console.log(total); // undefined
var total = 500;

with:

console.log(total); // ReferenceError
let total = 500;

The second version exposes the problem immediately.

8. What About const?

const also has a Temporal Dead Zone.

console.log(API_URL);
const API_URL = "/api/users";

Result:

ReferenceError:
Cannot access 'API_URL' before initialization

Conceptually:

Enter Scope
↓
API_URL binding exists
but is uninitialized
↓
TDZ
↓
const API_URL = "/api/users"
↓
Initialized
↓
Accessible ✅

But const has another important rule:

A const declaration must have an initializer.

This works:

const role = "Admin";

This doesn’t:

const role;

because a const binding cannot later be reassigned to provide its initial value.

9. let vs const: Reassignment

let allows reassignment:

let status = "pending";
status = "completed"; // ✅

const does not:

const status = "pending";
status = "completed"; // ❌ TypeError

A useful rule for modern JavaScript is:

Use const by default. Use let when the binding needs to be reassigned. Avoid var in modern application code unless you specifically need or are explaining its behavior.

10. Important const Interview Trap

const does not make an object immutable.

Consider:

const user = {
name: "Jashan",
role: "Developer",
};

user.role = "Senior Developer"; // ✅

This is allowed. Why?

Because we didn’t reassign the user binding.

The binding still refers to the same object.

user
│
▼
Object
{
name: "Jashan",
role: "Senior Developer"
}

What isn’t allowed:

user = {
name: "Another User",
}; // ❌

That attempts to reassign the user binding.

So remember:

const prevents reassignment of the binding. It does not automatically make the referenced value immutable.

This distinction matters constantly in React and JavaScript applications.

11. Scope + TDZ

TDZ belongs to the relevant lexical scope.

Get Jashandeep Kaur’s stories in your inbox

Join Medium for free to get updates from this writer.

Consider:

let status = "Global";
function showStatus() {
console.log(status);
let status = "Local";
}
showStatus();

What happens?

A common guess is:

Global

But the result is:

ReferenceError:
Cannot access 'status' before initialization

Why?

Because showStatus() has its own local status binding:

let status = "Local";

JavaScript’s identifier resolution finds that local binding first.

showStatus environment
│
├── status → exists but uninitialized
│
▼
FOUND

Once JavaScript finds that local binding, it does not continue searching for the global status.

But the local binding is still in its TDZ.

Therefore:

console.log(status);

throws a ReferenceError.

This beautifully connects Blog 2 with Blog 3:

Scope Chain
↓
Find nearest binding
↓
Binding found
↓
Is it initialized?
│
├── Yes → use value
│
└── No → ReferenceError

12. Redeclaration

Another major difference involves declaring the same identifier again.

var

var user = "Jashan";
var user = "John";
console.log(user);

Output:

John

var permits this kind of redeclaration in the same function/global var scope.

let

let user = "Jashan";
let user = "John";

This produces:

SyntaxError

const

const user = "Jashan";
const user = "John";

Also:

SyntaxError

This prevents accidental duplicate lexical declarations.

We’ll connect redeclaration, shadowing, and illegal shadowing more deeply in Blog 4.

13. undefined vs Uninitialized vs Not Defined

These terms are often confused in interviews.

undefined

console.log(a);
var a = 10;

Before assignment:

a exists
a is initialized
a's value = undefined

Therefore:

undefined

Uninitialized

console.log(a);
let a = 10;

Before initialization:

a binding exists
but is uninitialized

Trying to access it:

ReferenceError

This is the TDZ.

Not Defined / Unresolvable

console.log(age);

when no accessible age binding exists:

Current scope ❌
↓
Outer scope ❌
↓
Global ❌
↓
End

Result:

ReferenceError: age is not defined

So:

StateMeaningundefinedBinding exists and its value is undefinedUninitializedBinding exists but cannot yet be accessedNot defined / unresolvableNo accessible binding could be found

This distinction is extremely important for interviews.

14. Function Declarations and Hoisting

Hoisting doesn’t apply only to variables.

Consider:

greet();
function greet() {
console.log("Hello");
}

Output:

Hello

Why?

Function declarations are instantiated during setup so the function binding is available before execution reaches the declaration statement.

Conceptually:

SETUP
greet → function available
↓
EXECUTION
greet()
→ "Hello"

This is why a function declaration can normally be called before its textual declaration.

15. Function Expression — Important Difference

Now consider:

greet();
var greet = function () {
console.log("Hello");
};

What happens?

During setup:

greet → undefined

because greet is a var binding.

Then execution reaches:

greet();

which is effectively attempting:

undefined();

Result:

TypeError: greet is not a function

This is different from:

function greet() {
console.log("Hello");
}

The difference comes from how the binding is established, not because JavaScript moves one piece of source code around differently.

16. What About let / const Function Expressions?

Consider:

greet();
const greet = function () {
console.log("Hello");
};

Here greet is a const binding.

Before its declaration is initialized:

greet → uninitialized

So calling it results in:

ReferenceError:
Cannot access 'greet' before initialization

The same idea applies to arrow functions:

greet();
const greet = () => {
console.log("Hello");
};

Again:

ReferenceError

because the important question is:

How was the variable holding the function declared?

Here it was declared with const, so TDZ rules apply.

17. Hoisting Mental Model

Don’t memorize:

JavaScript physically moves declarations
to the top of the file. ❌

Instead think:

JavaScript prepares the environment
↓
Declarations are processed
↓
Bindings are established
↓
Different declarations are initialized differently
↓
Normal execution begins

Simplified:

BEFORE NORMAL EXECUTION

var → binding initialized with undefined
let → binding uninitialized
const → binding uninitialized
function declaration → function binding available

Then:

NORMAL EXECUTION
Assignments happen
Statements execute
Functions are invoked

That’s a much stronger understanding of hoisting.

18. Real-World React / TypeScript Usage

In modern React code, you’ll commonly write:

function PatientCard({ patient }: PatientCardProps) {
const fullName = `${patient.firstName} ${patient.lastName}`;
const handleOpenProfile = () => {
console.log(fullName);
};
return (
<button onClick={handleOpenProfile}>
{fullName}
</button>
);
}

Notice that we use const for:

fullName
handleOpenProfile

because those bindings are not reassigned within that render.

If something needs reassignment:

let total = 0;
for (const item of items) {
total += item.price;
}

let makes sense for total.

Modern rule:

Need reassignment?
│
┌──┴──┐
│ │
No Yes
│ │
▼ ▼
const let

var is generally avoided in new application code because its function scope, redeclaration behavior, and initialization semantics can make code less predictable.

19. Common Mistakes

1. “let and const are not hoisted."

Misleading.

Their declarations are processed and bindings established before normal execution, but they remain uninitialized until their declaration is evaluated.

2. “Hoisting moves code to the top.”

Not literally.

Hoisting describes observable behavior resulting from how declarations and bindings are established before normal execution.

3. “const means immutable."

Wrong.

const user = {};
user.name = "Jashan"; // ✅

const prevents binding reassignment, not object mutation.

4. “TDZ means the variable doesn’t exist.”

Wrong.

The binding exists, but it is uninitialized and inaccessible during the TDZ.

5. “undefined and TDZ are the same."

Wrong.

var → initialized with undefined
let/const in TDZ → uninitialized

These are different states.

Key Takeaways

  • Hoisting does not mean JavaScript physically moves code.
  • Declarations are processed before normal execution.
  • var bindings are initialized with undefined.
  • let and const bindings are initially uninitialized.
  • let and const therefore have a Temporal Dead Zone.
  • TDZ lasts until the declaration is evaluated and the binding is initialized.
  • var is function-scoped.
  • let and const are block-scoped.
  • let allows reassignment.
  • const prevents reassignment of the binding.
  • const does not automatically make objects immutable.
  • let and const cannot be redeclared in the same lexical scope in the way var can.
  • Function declarations are available before their declaration line executes.
  • Function expressions follow the hoisting/TDZ behavior of the variable declaration holding them.
  • undefined, uninitialized, and unresolvable identifiers are different concepts.

Quick Mental Model

SCOPE SETUP
│
┌─────────┼──────────┐
│ │ │
var let const
│ │ │
▼ ▼ ▼
initialized uninitialized uninitialized
with │ │
undefined └────┬─────┘
│
▼
TDZ
│
▼
declaration executes
│
▼
initialized

--

--

Jashandeep Kaur
Jashandeep Kaur