Hello! Welcome back to our course.
In our last lesson, we demystified type coercion by examining the internal algorithms JavaScript uses, like ToPrimitive, to handle operations between different data types. We established that what seems like quirky behavior is actually a predictable, rule-based system.
Today, we'll apply that same "under-the-hood" approach to another fundamental aspect of JavaScript: variable declarations. You've likely used var, let, and const extensively, but there are crucial differences in how the JavaScript engine treats them. By the end of this lesson, you will be able to explain the difference between var, let, and const in terms of scoping, hoisting, and the Temporal Dead Zone (TDZ). This knowledge is vital for writing bug-free, predictable, modern JavaScript.
Hoisting: More Than Just "Moving to the Top"
You may have heard hoisting described as "JavaScript moving variable declarations to the top of the scope." While a useful mental shortcut, it's not what actually happens. Hoisting is a direct consequence of how the JavaScript engine's execution context works.
Recall that an execution context is created in two phases:
- Creation Phase: The engine scans the code, allocates memory for variables and functions, but does not execute any code.
- Execution Phase: The engine executes the code line by line.
The difference between var, let, and const begins in the creation phase.
let & const in JS π₯Temporal Dead Zone | | Namaste JavaScript Ep. 8
Let's start by addressing a common point of confusion: are let and const hoisted? This video from the popular 'Namaste JavaScript' series gives a clear initial answer and introduces the core concepts we'll be exploring.
Watch from the beginning to 04:36. Focus on: The initial statement that let and const are hoisted. The demonstration of how var is attached to the global object (window in browsers), while let and const are stored in a separate, inaccessible memory space before initialization.
As the video demonstrates, all three declaration types are "hoisted" in the sense that the engine is aware of them and allocates memory for them during the creation phase. The critical difference is their initial state:
var: Hoisted and initialized with the valueundefined.let&const: Hoisted but remain uninitialized.
This "uninitialized" state is the key to understanding our next concept.
The Temporal Dead Zone (TDZ)
The fact that let and const variables are hoisted but uninitialized creates a state known as the Temporal Dead Zone (TDZ).
The TDZ is the period of time from the beginning of a variable's enclosing scope until the point where its declaration is processed during execution. If you try to access a let or const variable within its TDZ, you will get a ReferenceError.
let & const in JS π₯Temporal Dead Zone | | Namaste JavaScript Ep. 8
The 'separate, inaccessible memory space' the video mentioned has a name: the Temporal Dead Zone. Let's see how the video defines and demonstrates this.
Continue watching from 04:36 to 07:50. Pay close attention to the definition of the TDZ as the phase from when a variable is hoisted until it is initialized. Notice how trying to access the variable a within this zone results in a ReferenceError.
Now, let's connect this behavior directly to the mechanics of the execution context to understand why this happens.
How Execution Context Works in JavaScript
To solidify our understanding, let's see how this behavior is a direct result of the engine's creation and execution phases. The video showed us what happens; this article explains how and why.
In this article from freeCodeCamp, read the sections under the heading 'Creating a Deeply Nested Function Execution Context', specifically 'Understanding var, let, and const' and 'Why the Difference Between var and let/const?'. Focus on: How the engine allocates memory for all three during the 'Loading Phase' (another name for the Creation Phase). The formal definition of the TDZ and its purpose. The historical reason for the different behaviors: ES5's fallback (undefined for var) vs. ES6's goal of safer, more predictable code with let and const.
The TDZ isn't a bug; it's a feature designed to help you write better code. It prevents you from accidentally using a variable before it has been properly declared and initialized, a common source of bugs with var.
This image provides a great visual model for this concept.

Scope: Function vs. Block
The second major difference is their scopeβthe region of code where they are accessible.
varis function-scoped: It is accessible anywhere within the function it's declared in, regardless of blocks ({...}). If declared outside any function, it has global scope.letandconstare block-scoped: They are only accessible within the block ({...}) in which they are defined. This includesifstatements,forloops, or even just a standalone pair of curly braces.
This difference has profound implications. Consider the classic for loop problem with setTimeout:
// Using var
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // What will this log?
}, 100);
}
Because var is function-scoped, there is only one i variable that is shared across all loop iterations. By the time the setTimeout callbacks execute (long after the loop has finished), the value of i is 3. The output will be 3, 3, 3.
Now, let's see the same code with let:
// Using let
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // And what about this?
}, 100);
}
Because let is block-scoped, a new i variable is created for each iteration of the loop. Each setTimeout callback captures a different i with the value it had during that specific iteration. The output is, as you'd intuitively expect, 0, 1, 2. This behavior is almost always what is desired and is a primary reason to prefer let over var in loops.
Mutability and Re-declaration
The final differences relate to changing a variable's value and declaring it again.
-
Re-assignment:
const: Cannot be re-assigned. Once a value is assigned, the variable name cannot be pointed to a new value.
It's crucial to remember the concept of mutability from our earlier lesson. For objects and arrays declared withconst a = 10; a = 20; // Throws TypeError: Assignment to constant variable.const, the variable cannot be reassigned, but the contents of the object or array can be changed.const user = { name: "Alex" }; user.name = "Bob"; // This is perfectly fine. // user = { name: "Charlie" }; // This would throw a TypeError.let: Can be re-assigned.var: Can be re-assigned.
-
Re-declaration:
var: Allows re-declaration within the same scope without error.var x = 1; var x = 2; // No error, x is now 2.letandconst: Do not allow re-declaration in the same scope. This prevents you from accidentally overwriting a variable and is another key safety feature.let y = 1; let y = 2; // Throws SyntaxError: Identifier 'y' has already been declared.
let & const in JS π₯Temporal Dead Zone | | Namaste JavaScript Ep. 8
Beyond hoisting and scope, var, let, and const also differ in how they handle re-assignment and re-declaration. This can lead to different types of errors. The 'Namaste JavaScript' video provides an excellent breakdown of these.
Watch the section from 07:50 to 17:53. This is a longer segment, but it's rich with detail. Focus on understanding the distinction between: ReferenceError: Accessing an uninitialized variable (in the TDZ) or a non-existent one. TypeError: Attempting an invalid operation, like re-assigning a value to a const variable. SyntaxError: Code that violates the language's grammar, like re-declaring a let variable or declaring a const without an initializer.
Summary and Best Practices
Here's a summary table of the key differences:
| Feature | var |
let |
const |
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisting | Yes, initialized to undefined |
Yes, uninitialized (TDZ) | Yes, uninitialized (TDZ) |
| Re-assignment | Yes | Yes | No |
| Re-declaration | Yes | No | No |
| Global Object | Attaches to window object |
Does not attach | Does not attach |
Given these differences, modern JavaScript development follows a clear set of best practices.
let & const in JS π₯Temporal Dead Zone | | Namaste JavaScript Ep. 8
Finally, let's consolidate this into a set of practical guidelines for writing modern JavaScript.
Watch the concluding part of the video from 17:53 to 20:01. It gives clear recommendations on when to use const, let, and why you should generally avoid var.
The community consensus is:
- Prefer
constby default. This makes your code more predictable by signaling that a variable's assignment should not change. - Use
letonly when you know a variable needs to be re-assigned, such as a loop counter or a value that changes based on application state. - Avoid
varcompletely in modern (ES6+) codebases. The function-scoping and loose hoisting behavior are common sources of bugs thatletandconstwere designed to solve.
Conclusion
You now have a deep, conceptual understanding of how var, let, and const operate within the JavaScript engine.
Key Takeaways:
- All three are "hoisted," but their initial states differ:
varis initialized toundefined, whileletandconstremain uninitialized. - The Temporal Dead Zone (TDZ) for
letandconstprevents access to them before their declaration, resulting in aReferenceErrorand promoting safer code. varis function-scoped, whileletandconstare block-scoped, which has a significant impact on behavior in loops and conditional blocks.constenforces assignment immutability (the variable cannot be re-assigned), whileletandvarallow it.- The modern best practice is to favor
const, useletwhen necessary, and avoidvar.
What's Next?
We've seen that the Execution Context is central to understanding hoisting. In our next lesson, we will formalize this concept completely. You will learn to describe how the JavaScript engine creates execution contexts and manages the scope chain during code execution. This will connect many of the dots we've discussed so far and lay the groundwork for understanding even more advanced concepts like closures.