DEV Community

Justin Mitchell
Justin Mitchell

Posted on • Edited on

const, let, var; JavaScript variables and immutability

Originally published → https://thejs.dev/jmitchell/const-let-var-javascript-variables-and-immutability-a6c

The release of ES2015 in 2015 introduced two new keywords, let and const, which are important block-scoped variables in JavaScript. Until that point, for close to two decades, a variable was declared in JavaScript by using the var keyword.

var message = "Hello, world!"
Enter fullscreen mode Exit fullscreen mode

So, what's the difference?

The three keywords together provide the same fundamental functionality, but differ in the way that they provide it. var and let are reassignable values, while const has limited mutability.

var and let can be reassigned - which is the act of setting a value at one point in time, and then using the same variable reference and giving it a new value.

var fruit = "Apple";
// ... do something
if (foo === 1) {
  fruit = "Orange";
}
Enter fullscreen mode Exit fullscreen mode

The same can be achieved with let:

let fruit = "Apple";
// ... do something
if (foo === 1) {
  fruit = "Orange";
}
Enter fullscreen mode Exit fullscreen mode

However, const does not allow reassignment of the reference, nor does it allow the mutation of a primitive. A primitive is a one of six types; a string, boolean, number, bigint, undefined and symbol, while null is a special type of object.

All primitives are immutable, meaning that their values cannot be changed, but they can be assigned a new value. This is different to stricter and strongly typed languages, where a primitive can mutable because of the way the language works.

Variable mutation through reassignment:

const foo = "Bar";
foo = "something else";
Enter fullscreen mode Exit fullscreen mode

You can however, mutate an object that declared as a const:

const foo = [];
foo.push("Apple");
foo.push("Orange");
// Foo is now [ "Apple", "Orange" ]
Enter fullscreen mode Exit fullscreen mode

What you can't do, is set a new value on a previously declared const by reassignment:

const foo = [];
const bar = {};
foo = bar;
// Uncaught TypeError: Assignment to constant variable. 
Enter fullscreen mode Exit fullscreen mode

Why you shouldn't use var

let, var and const behave in the same way in scope, other than the limited mutability for const. It is recommended however, to make it clear as to behaviour and treatment of a given property and its value, that you declare mutable variables as let and immutable variables as const.

var can still be used, but the treatment around var is far more flexible than let. For example, using a var, you can do this:

var x = "1";
var x = "2";
// x will have value of "2"
Enter fullscreen mode Exit fullscreen mode

Doing the same with let will throw an error, indicating the the variable has already been declared within the scope of execution

var x = "1";
var x = "2";
// Uncaught SyntaxError: Identifier 'x' has already been declared 
Enter fullscreen mode Exit fullscreen mode

What's the difference with variable scope?

var is a tricky one to deal with, because even with block-scoped execution, that is an if-else statement, a while loop, or anything executed between curly braces {}, var doesn't adhere to expected declaration rules, which is that anything declared in a scope, lives in that scope.

For example, this is possible, because when a var is declared outside of a function scope, it will mutate the parent scope, which in a lot of cases, is the window:

if (true) {
  var t = "foo";
}

console.log(window.t);
// Prints "foo" to the console
Enter fullscreen mode Exit fullscreen mode

By using var inside of a function, the declaration is block limited:

function foo() {
  var fooT = "foo";
}

foo();
console.log(window.fooT);
// prints "undefined" to the console
Enter fullscreen mode Exit fullscreen mode

This is where let and const come in, because they are correctly block scoped, thereby adhering to the rules of declaration within a function scope, conditional block scope or loop scope.

Importantly, they also don't modify the parent scope:

let fooBar = "foo";
console.log(window.fooBar);
// prints "undefined" to the console
Enter fullscreen mode Exit fullscreen mode

What about using var or let in a for loop?

There are multiple ways to iterate through a collection of objects in JavaScript, with while and for being to primary ways.

Below are several ways to iterate through an array collection using for

// for: i is the index
for (let i = 0; i < collection.length; i++) { collection[i] }
// for...in: x is the index
for (let x in collection) { collection[x] }
// for...of: x is the value
for (let x of collection) { x }
Enter fullscreen mode Exit fullscreen mode

The benefit of using let in a loop, is that let is block-scoped to the loop itself. That is to say, that you cannot consume the declaration of let outside of the block that it is declared in.

// Using let
const collection = [ "apple", "orange" ];
for (let fruit of collection) {
  console.log("I like to eat 1 " + fruit);
}
console.log(fruit);
// Uncaught ReferenceError: fruit is not defined 
Enter fullscreen mode Exit fullscreen mode

However, when using var, you can still access the fruit variable:

// Using let
const collection = [ "apple", "orange" ];
for (var fruit of collection) {
  console.log("I like to eat 1 " + fruit);
}
console.log(fruit);
// prints "orange" to the console
Enter fullscreen mode Exit fullscreen mode

There are ways around this, such as dropping the declaration of var inside block scopes, but this opens up another entire can of worms with readability, consistency and correct block scoping.

The bigger issue at hand, is that even when dropping var, let or const in a loop, the variable is automatically declared as a var:

const collection = [ "apple", "orange" ];
for (var fruit of collection) {
  console.log("I like to eat 1 " + fruit);
}
console.log(fruit);
// prints "orange" to the console
Enter fullscreen mode Exit fullscreen mode

I sort of understood some of those words...

To clarify the meaning of some of the words and terms used:

  • mutate is to change, or modify a variable or property of an object, eg let foo = 1; foo = 2;
  • assignment is to set a value to a variable eg let foo = 1;
  • iterate is to traverse, or navigate from top-to-bottom (left to right) of an array or collection, starting with the first element and ending in the last element
  • collection is a structured object that has n elements, eg 1 or more elements, eg. const foo = [ "apple", "orange" ];

Top comments (4)

Collapse
 
savagepixie profile image
SavagePixie • Edited

var and let are mutable values

However, const does not allow reassignment of the reference, nor does it allow the mutation of a primitive.

Actually all promitives are always immutable in JavaScript, whereas referential types are mutable. Mutability in JavaScript is not related to the variable keyword but to the value's type.

The difference between let and const is about reassignability, not mutability.

Collapse
 
jmitchell38488 profile image
Justin Mitchell • Edited

Good point I'll update the language around that.

Collapse
 
ziizium profile image
Habdul Hazeez

Additional literature by Dan Abramov:

overreacted.io/on-let-vs-const/

Collapse
 
jmitchell38488 profile image
Justin Mitchell • Edited

Yep, this article is aimed at beginners and those learning it for the first time. Dan Abramov discusses let/const and using eslint.