Cover image for var vs let | let vs const

Arun Prakash Pandey

This post covers concepts related to the use of var,let and const keywords in ES6.
We will compare based on the below various properties like

  1. scope
  2. hoisting and accessibility
  3. this
  4. assignment and redeclaration
  5. temporal dead zone and accessibility
  6. error handling

Before jumping on to the applicable differences, let us first understand the meaning of "scope".

Scope

Scope of a 'variable' is the region of your program in which it is defined/accessible. And is generally defined using a block using braces({}).

NOTE: Scope is always associated with a declared identifier (such as a variable, a function, a class, or a parameter). It is not an independent entity; rather, it describes where that identifier is visible and can be accessed.

var is function-scoped & let is block-scoped

  • A variable declared using the 'var' keyword belongs to the entire function, even if declared inside a block.
function test() {
    if (true) {
        var x = 10;
    }

    console.log(x);
}

test();  // logs 10

Enter fullscreen mode Exit fullscreen mode

  • let belongs to the nearest block ({}).
function test() {
    if (true) {
        let x = 10;
    }

    console.log(x);
}

test(); // gives ReferenceError: x is not defined

Enter fullscreen mode Exit fullscreen mode

Global var becomes a property of globalThis

  1. In browsers: whether you do var x = 10; OR globalThis.x = 10
    both mean the same.

  2. But if one does

let y = 20;
console.log(globalThis.y); // logs undefined

Enter fullscreen mode Exit fullscreen mode

because it does not creates a property on the globalThis object.

If x was declared with var, it exists.
If it was declared with let, it doesn't.

Redeclaration

  • var allows redeclaration.
var a = 10;
var a = 20;

console.log(a); // logs 20

Enter fullscreen mode Exit fullscreen mode

function example() {

    for (var i = 0; i < 5; i++) {
    }

    for (var i = 0; i < 10; i++) {
    }

}
// There is actually only one i variable. 
// Each loop simply resets it.

Enter fullscreen mode Exit fullscreen mode

  • let does not allows redeclaration.
let a = 10;
let a = 20;
// gives SyntaxError

Enter fullscreen mode Exit fullscreen mode

function example() {

    for (let i = 0; i < 5; i++) {
    }

    for (let i = 0; i < 10; i++) {
    }

}
// These are two different variables.
// Each loop gets it's own _i_

Enter fullscreen mode Exit fullscreen mode

Hoisting

Hoisting is probably the most confusing for beginners. Legacy Js used only var which is accessible even before declaration and assignment. Like below code.

console.log(x); // logs undefined

var x = 5;

Enter fullscreen mode Exit fullscreen mode

Because it behaves similar to the below code

var x; // initialize

console.log(x); // access

x = 5; // assignment

Enter fullscreen mode Exit fullscreen mode

The declaration moves, the assignment stays where it is. As a result, we do not get any reference error.

However, later on we have let and const key words which assist with meaningful errors without returning undefined.

console.log(x);

let x = 5;
// ReferenceError: Cannot access 'x' before initialization

Enter fullscreen mode Exit fullscreen mode

Why?

because the variable is placed in the Temporal Dead Zone (TDZ) until the declaration executes. Here, the variable exists in the memory but is not accessible for the code.
These have a number of benefits such as :

  1. It provides block scope, making variables behave more intuitively.
  2. It prevents accidental redeclarations.
  3. It avoids the confusing behavior of var hoisting by enforcing the Temporal Dead Zone, so using a variable before its declaration results in a clear error instead of silently yielding undefined.

Thanks for reading, if you came this far, please mention your thoughts / suggestions in the comments.
Next article will cover optional chaining and short circuiting.