Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 26

Var, Let, and Const: Scoping, Hoisting, and TDZ

Recommended resources

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:

  1. Creation Phase: The engine scans the code, allocates memory for variables and functions, but does not execute any code.
  2. 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 value undefined.
  • 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.

JavaScript Execution Contexts, Scope Chain, and Variable Hoisting
This diagram illustrates how the JavaScript engine handles variables during the creation phase of an execution context. It shows that `var` variables are initialized to `undefined`, while `let` variables enter the Temporal Dead Zone (TDZ), making them inaccessible until their declaration is executed.

Scope: Function vs. Block

The second major difference is their scopeβ€”the region of code where they are accessible.

  • var is 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.
  • let and const are block-scoped: They are only accessible within the block ({...}) in which they are defined. This includes if statements, for loops, 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.
      const a = 10;
      a = 20; // Throws TypeError: Assignment to constant variable.
      
      It's crucial to remember the concept of mutability from our earlier lesson. For objects and arrays declared with 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.
      
    • let and const: 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:

  1. Prefer const by default. This makes your code more predictable by signaling that a variable's assignment should not change.
  2. Use let only when you know a variable needs to be re-assigned, such as a loop counter or a value that changes based on application state.
  3. Avoid var completely in modern (ES6+) codebases. The function-scoping and loose hoisting behavior are common sources of bugs that let and const were 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: var is initialized to undefined, while let and const remain uninitialized.
  • The Temporal Dead Zone (TDZ) for let and const prevents access to them before their declaration, resulting in a ReferenceError and promoting safer code.
  • var is function-scoped, while let and const are block-scoped, which has a significant impact on behavior in loops and conditional blocks.
  • const enforces assignment immutability (the variable cannot be re-assigned), while let and var allow it.
  • The modern best practice is to favor const, use let when necessary, and avoid var.

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.