Introduction
In our last lesson, we explored hoisting and saw how the JavaScript engine processes variable and function declarations during its memory creation phase. We learned that var declarations are initialized with undefined, while let and const enter a "Temporal Dead Zone" (TDZ), making them inaccessible until their declaration is executed.
Today, we will build directly on that foundation to create a complete picture of the differences between var, let, and const. This lesson will focus on three critical aspects: their scoping rules, their hoisting behavior (including the TDZ), and their rules around re-assignment and re-declaration. Mastering these distinctions is crucial for writing modern, predictable, and bug-free JavaScript, as they address some of the most common pitfalls of the language's earlier versions.
The Core Difference: Scope
The most significant and impactful difference between var and the modern let/const is how they handle scope. Scope determines where in your code a variable is accessible. In JavaScript, there are three main types: Global Scope, Function Scope, and Block Scope.

Let's watch a segment of a video that provides a clear, practical demonstration of these scope types.
Javascript Interview Questions ( Var, Let and Const ) - Hoisting, Scoping, Shadowing and more
This video from the "RoadsideCoder" channel clearly demonstrates the different scoping rules.
Watch the segment explaining scope from this timestamp. Pay close attention to how var behaves when placed inside a block versus how let and const behave.
As the video showed, var and let/const follow different rules:
Function Scope (var)
A variable declared with var is scoped to the nearest enclosing function. If it's not inside a function, it's scoped globally. It completely ignores block-level constructs like if, for, or standalone {} blocks. This behavior can lead to unexpected results.
Consider this classic example involving a for loop and setTimeout:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// Expected output might be 0, 1, 2.
// Actual output: 3, 3, 3
Why does this happen? Because the var i is function-scoped (or globally scoped here), there is only one i variable that all three setTimeout callbacks share a reference to. By the time the callbacks execute (after about 100ms), the loop has already completed, and the value of i is 3.
Block Scope (let and const)
let and const introduced block scope to JavaScript. This means a variable declared with let or const is only accessible within the nearest enclosing pair of curly braces {}, which includes if statements, for loops, and while loops.
Let's fix the previous example using let:
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// Actual output: 0, 1, 2
This works as expected because let creates a new i variable for each iteration of the loop. Each setTimeout callback captures a different instance of i with the value for that specific iteration (0, 1, and 2, respectively). This behavior is far more intuitive and helps prevent a common class of bugs.
Hoisting & The Temporal Dead Zone: Why `let` and `const` Behave ...
This article provides excellent code examples that contrast the block-scoping behavior of let with the function-scoping of var.
Please read the section titled Block Scoping in Practice. The side-by-side comparison for loops and if blocks makes the difference crystal clear.
Hoisting and the Temporal Dead Zone (TDZ) Revisited
We know from our last lesson that let and const are hoisted, but unlike var, they are not initialized. The period from the start of the block until the declaration line is the Temporal Dead Zone (TDZ).

The TDZ is not just a quirk; it's an intentional design feature to help you write better code. It prevents you from accidentally using a variable that hasn't been assigned a value yet, a common source of bugs with var.
Let's dive deeper with an excellent video that uses the browser's debugger to show you exactly what's happening under the hood.
let & const in JS 🔥Temporal Dead Zone | | Namaste JavaScript Ep. 8
Akshay Saini's "Namaste JavaScript" series provides one of the best explanations of the TDZ.
Watch the following segments to solidify your understanding: Initial Comparison: Observe the immediate difference in behavior when accessing let vs. var before declaration. Debugger Deep Dive: This is the key part. Notice how the debugger shows let and const variables existing in a separate "Script" memory space, distinct from var variables on the global object. This is why they are inaccessible before initialization. Defining the TDZ: This segment provides a concise definition of the TDZ as the phase from hoisting until the variable is initialized.
The video makes it clear: let and const are hoisted (memory is set aside), but they are kept in an uninitialized state until the JavaScript engine executes their declaration line. Trying to access them before this point results in a ReferenceError. This contrasts sharply with var, which is initialized with undefined, allowing you to access it without an error, potentially masking bugs.
Re-declaration and Re-assignment
The final key differences lie in whether you can re-declare or re-assign values to these variables.
Javascript Interview Questions ( Var, Let and Const ) - Hoisting, Scoping, Shadowing and more
The "RoadsideCoder" video also has a great breakdown of these rules.
Please watch the segments covering declaration, initialization, and re-initialization. This will quickly cover the rules for all three keywords.
Here is a summary of the rules:
-
var: Allows both re-declaration and re-assignment within the same scope. This can be problematic as you can accidentally overwrite a variable without any warning.var a = 10; var a = 20; // No error a = 30; // No error console.log(a); // 30 -
let: Allows re-assignment but does not allow re-declaration in the same scope. This prevents accidental overwrites.let b = 10; // let b = 20; // SyntaxError: Identifier 'b' has already been declared b = 30; // This is fine console.log(b); // 30 -
const: Allows neither re-declaration nor re-assignment. The variable must be initialized at the time of declaration.const c = 10; // const c = 20; // SyntaxError: Identifier 'c' has already been declared // c = 30; // TypeError: Assignment to constant variable. console.log(c); // 10
A crucial point about const that trips up many developers: it creates a constant binding, not a constant value. This means that while you cannot re-assign the variable itself, you can mutate the contents of the object or array it refers to.
const user = { name: "Alice" };
// user = { name: "Bob" }; // This is illegal (TypeError)
user.name = "Bob"; // This is perfectly legal!
console.log(user.name); // "Bob"
Summary and Best Practices
We've covered the three major areas of differentiation. This table summarizes everything concisely.
| Feature | var |
let |
const |
|---|---|---|---|
| Scope | Function-scoped | Block-scoped | Block-scoped |
| Hoisting | Hoisted and initialized | Hoisted, but not initialized | Hoisted, but not initialized |
| Before Declaration | undefined |
ReferenceError (TDZ) |
ReferenceError (TDZ) |
| Re-declaration | Allowed in the same scope | Not allowed in the same scope | Not allowed in the same scope |
| Re-assignment | Allowed | Allowed | Not allowed |
| Must be Initialized? | No | No | Yes |
Given these behaviors, the modern JavaScript best practice is:
- Use
constby default. This makes your code more predictable by preventing accidental re-assignment. - Use
letonly when you know you need to re-assign a variable, such as a loop counter or a value that needs to be updated. - Avoid using
var. Its function-scoping and hoisting behavior can lead to subtle bugs thatletandconstare designed to prevent.
Conclusion
In this lesson, we systematically compared var, let, and const, solidifying your understanding of their roles in modern JavaScript. You can now confidently explain the differences in their scoping mechanisms, hoisting behavior, and rules for modification.
Key Takeaways:
- Scope:
varis function-scoped, leading to potential issues in loops and blocks.letandconstare block-scoped, providing more intuitive and safer behavior. - Hoisting & TDZ: All three are hoisted, but
varis initialized toundefined, whileletandconstenter the Temporal Dead Zone (TDZ) and throw aReferenceErrorif accessed before their declaration line. - Mutability:
varandletcan be re-assigned, butconstcannot. However,constdoes not make objects or arrays immutable; their contents can still be changed.
In our next lesson, we will explore the scope chain. Having just mastered the rules of scope for individual variables, we will now see how JavaScript resolves variable names when you have functions nested inside other functions.