Let vs Var vs Const: Temporal Dead Zone, Scopes, Shadowing & More

Let vs Var vs Const: Temporal Dead Zone, Scopes, Shadowing & More

With the introduction of ES6, a lot of new features have been added in JavaScript. One of them is the addition of let & const along with the good old var keyword for variable declaration. Many times, we use these keywords without considering what happens behind the scenes & minute differences among them. Let's see how these 3 do the same job but in a different way. Just a heads-up, this is going to be a long article with a lot of debugging & examples.

Hoisting & Temporal Dead Zone

When we declare a variable using var, they are hoisted, check this article, but some might say that is not the case with let & const, which is wrong. Variables are hoisted with let & const as well but there is a catch. Let's dive in a bit deeper into the debugger.

Let's look at the following program:

var a = 1;

let b = 2;

const c = 3;

According to the previous article, all of these variables should print undefined as they are being printed to the console before their declaration. But that is not the case. For first console (var declaration & initialization), it prints undefined, but for the other two, it throws a ReferanceError.


The reason behind this error is let & const are hoisted in a different way than var. They are not the part of the Global object, i.e. you cannot access them via the window object. You can see this in the below screenshot. The other two variables are a part of separate Script object which is not included in the Global scope, i.e. they are stored in a reserved space.


When we declare a variable using let & const, it cannot be accessed before they are declared. This is known as the Temporal Dead Zone (TDZ). TDZ is the temporary state of the variable before it's declaration in a block.

The variable is said to be in a "Temporal Dead Zone" (TDZ) from the start of the block until the declaration has completed.

It is a temporary zone as it depends on the execution order rather than the order in which the code is written. For eg. the below code will work fine as the function is executed after the declaration of the variable.

// zone starts for variable "a"
function hello() {

// inside the zone for variable "a"

let a = "Hello";
// zone ends for the variable "a"

hello(); // outside the zone

Reassignment & Redeclaration

Let's have a look at the code below:

// redeclaration
var a = 1;
var a = 2; // no error

let b = 1;
let b = 2; // syntax error

const c = 1;
const c = 2; // syntax error

// reassignment
var a = 1;
a = 2; // no error

let b = 1;
b = 2; // no error

const c = 1;
c = 2; // type error

// special case
const a; // syntax error
a = 1;

const a = 1; // no error

Variables declared with var can be redeclared, but for let & const they throw a SyntaxError on redeclaration.

As the name suggest const variables cannot be reassigned with a different value, they will throw a TypeError, but you can reassign let & var.

Note: When declaring variables with const, the initialization must happen during the declaration otherwise it will throw a SyntaxError, i.e. declaration & initialization should be done in the same expression or line.


You might have heard that let & const are block scoped, but what does this mean. Let's take an example.

let a = 1;

if (!null) {
  let b = 2;
  var c = 3;

  console.log(b, c);


First, let's understand what is a block. A block is a set of multiple statements. It is also called a compound statement. If you look at the above program, the statements inside the if condition are all in a block. Let's have a look at the debugger for the above program.

  • Breakpoint 1, Line 2: Here we a simple variable a declared using ```let

- **Breakpoint 2, Line 8:** Here we can see that **apart from the Script & Global we also have Block inside the scope** as we have a block of code inside the program & we have some variables inside it. This is **Block** scope & the variable with ```let``` is a part of this scope. This is why ```let``` & ```const``` are considered as block scoped variables. The variable ```c``` is declared using ```var```, that is why it is not inside the **Block** scope. You can see the variable ```c``` in the **Global** scope itself. ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647973941386/WNouQDxrS.png)

- **Breakpoint 3, Line 11:** As explained in the previous breakpoint, ```c``` is in **Global** scope as it is declared using ```var``` keyword, we can access it outside the block as well. Also, after the execution of the code inside the block, it's scope is deleted. ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647973954439/boBOtuYxg.png)

### Shadowing in Var

Before we see how these three keywords differ in terms of shadowing, let's see what is shadowing with an example.

var a = 1;


function hello() { var a = 3; console.log(a); }


if (!null) { var a = 2; console.log(a); }

Let's put debugging breakpoints in the above program.

- **Breakpoint 1, Line 3:** Here we just have a variable ```a``` declared in the **Global** scope. ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647973971280/yDtHtJrz1.png)

- **Breakpoint 2, Line 7:** Here we have a function ```hello()``` where we are redeclaring the variable ```a```. But this being a function, a new execution context is created, so a new local scope is created. [Refer to this article](https://pulpmint.hashnode.dev/execution-context-and-call-stack-how-javascript-code-is-executed) for in-depth explanation. ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647973987520/DkfdAsdKM.png)

- Breakpoint 3, Line 14: It is similar to the previous breakpoint, but it is a block of code instead of a function. Due to this, **no new scope is created** & the newly redeclared variable ```a``` replaces the variable ```a``` in the **Global** scope. This is called **Shadowing**. In short, **line 3 & 14 represents the same memory space**. ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647974000386/9WoGmompG.png)

### Shadowing in Let & Const

Now that we are clear on how shadowing works let's see how things are different with ```let``` & ```const```. In the code snippet below, we are just using ```let``` instead of ```var```. We can also use ```const```, it will behave in the same way.

let a = 1;


if (!null) { let a = 2;

console.log(a); }


- **Breakpoint 1, Line 3:** At this point, we have the variable ```a``` in the reserved memory space. ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647974020231/WaXeGJ_nY.png)

- **Breakpoint 2, Line 8:** At this point, **a new scope is created** for the if block & a new variable is created in that scope as well. Unlike in the previous example, the **declaration in the the if block is not replacing the value of the previous variable**. This is because **both are pointing to a different memory location** & are in different scope. ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647974048953/kNNO9gkZ7.png)

- **Breakpoint 3, Line 11:** At this point the **block scope for the if condition is deleted** after it's execution & now the **control returns back to the reserved memory space** & the old value is printed. So in case of declarations with ```let``` the compiler tries to find the variable in the nearest scope. ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647974063807/k_BzemfWF.png)

### Illegal Shadowing

Whenever we **try to shadow a ```let``` or a ```const``` with a ```var``` inside a block**, it is called as Illegal Shadowing. It is not allowed in JavaScript & it will always **result in an SyntaxError**. But the reverse is possible i.e. if we try to shadow a ```var``` with a ```let``` or a ```const```, it is possible.

// illegal shadowing let a = 1; if (!null) { var a = 2; // syntax error }

// legal shadowing var b = 1; if (!null) { let b = 2; // no error }

### Summary

1. Below table contains the comparison between all the three keywords. 

2. Temporal Dead Zone is **state of the variable before it's declaration in a block**. it **depends on the execution order** of the code.

3. Shadowing is when a variable inside a inner block scope replaces the variable in it's outer block scope.

4. Illegal Shadowing is when **a declaration beyond the scope boundary is shadowed**. It usually happens when we try to shadow a ```let``` or a ```const``` with ```var``` in the child block scope.