JavaScript: var, let and const explained simply

The New York Times homepage in 1996 The New York Times homepage in 1996

Learning JavaScript can be tricky. It was born in 1995, and it was designed as a simple scripting language aiming to provide back then’s simple web pages some simple interaction capabilities. Did I already say simple?

Almost thirty years later, JavaScript has been the most popular language for the ninth year in a row. It powers web apps of unimaginable complexity by the standards of the time, web servers, desktop and mobile applications and literally anything you can imagine.

Its long and relatively troubled history is reflected in the language’s features. In JavaScript, even the most fundamental aspect of most programming languages — how you declare variables — has been affected by this huge shift in use cases and popularity.

Let’s see the biggest differences between the three ways you can do it: var, let and const.

Blocks and Scope

To understand these differences we need to grasp the basics of scoping. In many programming languages, including JavaScript, any portion of code delimited by curly brackets is a block.

if (true) {
    // I'm a block!
}

for (let i = 0; i < arr.length; i++) {
    // I'm a block too
}

function findMax(element) {
    // Also a block
}

The scope of a variable is the region of your code the variable is “visible” in. You can think of visibility as the ability to access a certain variable without it being undefined or receiving an error.

There can be many scoping rules, but the most common is block scoping. In block scoping, every region of code delimited by curly brackets (block) defines a new area of visibility (scope).


/* Remember that any portion of code delimited
by curly brackets is a { block } .
*/
let outsideVariable = 5

if (true) {
    let insideVariable = "Hello world!"
    // The variable "outsideVariable" is visible here.
}

// Variable "insideVariable" isn't visible here.

Scopes, or areas of visibility, can be nested. The direct consequence of this is that you can re-declare a variable already exising in an upper scope without interfering with the original variable at all.

let outsideVariable = 5

if (true) {
    let insideVariable = "Hello world!"
    // The variable "outsideVariable" is visible here.

    let outsideVariable = "I'm a string now"
    // Inside this block, "outsideVariable" was reassigned
    // and therefore has another meaning.
}

// outsideVariable is still the number 5 here.

The difference between var, let and const

Var#

Variables declared using the var keyword don’t follow block scoping. They still have scoping rules, but blocks on their own have no meaning for them:


/* Remember that any portion of code delimited
by curly brackets is a { block } .
*/
var outsideVariable = 5

if (true) {
    var insideVariable = "Hello world!"
    // The variable "outsideVariable" is visible here.
}

// Variable "insideVariable", this time, IS visible here.

Instead of block scoping, var variables follow function scoping: only functions can delimit their visibility.


/* Remember that any portion of code delimited
by curly brackets is a { block } .
*/
var outsideVariable = 5

function submitForm {
    var insideVariable = "Hello world!"
    // The variable "outsideVariable" is visible here.
}

// Variable "insideVariable" is not visible here.

There are other features exclusive to var variables, but its confusing scope can easily lead to all kind of bugs and therefore their use is discouraged.

let and const#

Variables declared using let and const are always block scoped, as shown in the previous examples. What’s the difference between them?

You can re-assign let variables as many times as you wish. To assign or re-assign means to use the = operator.

let a = "hello"

a = 5 // it's ok to re-assign

if (a === 5) {
    a = "world" // and so on...
}

On the other side, const variables can’t be re-assigned.

const a = "hello"

a = 5 // error!

Once assigned, a const variable can only refer to the same entity in memory.

The const keyword doesn’t make the entity it’s referring to immutable. Consider an array:


const arr = [1, 2, 3, 4, 5]

You’ll still be able to add or remove elements from this array:


const arr = [1, 2, 3, 4, 5]

arr.push(6) // array is now [1, 2, 3, 4, 5, 6]

Remember: as long as you aren’t using the assignment operator = on a const variable, any other operation is allowed.