JavaScript - var, let and const - and when to use them

Posted by Gjermund Bjaanes on June 19, 2016

ES6 (ES2015) introduced the new keywords let and const, to be used side by side with var.

In this post, we’ll take a closer look at all three, how they differ, and when to use them.

 

With ES6, you can use the let and the const keyword in basically the same places as you would the var keyword.

But, what are the differences?

To understand that, we first need to understand var properly.

The var keyword

With var, the variables you create are function scoped.

That means that they are available inside the function it was created, as well as any nested functions.

To demonstrate this quickly, take a look at this code:

function test() {
var myFunctionScopedVariable = 42;

console.log(myFunctionScopedVariable); // 42

function nestedTest() {
console.log(myFunctionScopedVariable); // 42
}

nestedTest();
}

test();
console.log(myFunctionScopedVariable); // ReferenceError: myFunctionScopedVariable is not defined

You can access the variable inside the function, and all nested functions, but not outside it.

As long as the variable is declared inside the function, it’s available in the entire function.

Lets take a look at an example where this is slightly problematic:

function test() {

for (var i = 0; i < 3; i++) {
var myFunctionScopedVariable = 42;
}

console.log(myFunctionScopedVariable); // 42
console.log(i); // 3
}

Both the variable “i”, and the variable “myFunctionScopedVariable” is available after the loop.

This is not so cool. It’s almost never what you want. Ugh. It just feels wrong…

But that is they way that var works.

 

There is actually another quirk with var, and that is hoisting. It’s not really specific to var, but it’s behaviour is a bit weird.

What hoisting does, is that it moves all declarations to the top of it’s scope. However, it does not move the initialization.

What that means in practice, is that this code block:

console.log(typeof test); // "function"

function test() {

for (var i = 0; i < 3; i++) {
var myFunctionScopedVariable = 42;
}
}

will be changed to something like this:

function test() {
var i;
var myFunctionScopedVariable;

for (i = 0; i < 3; i++) {
myFunctionScopedVariable = 42;
}
}

console.log(typeof test); // "function"

AND, to highlight the initialization part more clearly too (Thanks to Umamaheswararao Meka for pointing this out!)

console.log(willBeHoisted) //undefined
var willBeHoisted;

This will actually be translated to:

var willBeHoisted;
console.log(willBeHoisted) //undefined

which is why it does not behave the same way as:

console.log(what) // Uncaught ReferenceError: what is not defined

 

The let keyword

Now that you mostly understand what goes on with var, we can start looking at let, and how it actually works, compared to var.

 

The main difference between var and let is that let is block scoped, instead of function scoped.

This means that a lot of the crazyness we saw above, is not going to happen with let. Phew!

More specifically, a variable created with the let keyword is available inside the block it was created, and any nested blocks as well.

Also, it acts like it is not hoisted, but I’ll come back to that.

 

The first example still works the same way with let:

function test() {
let myFunctionScopedVariable = 42;

console.log(myFunctionScopedVariable); // 42

function nestedTest() {
console.log(myFunctionScopedVariable); // 42
}

nestedTest();
}

test();
console.log(myFunctionScopedVariable); // uh, oh! Error still!

The second example, however, will now start throwing some errors our way:

function test() {

for (let i = 0; i < 3; i++) {
let myFunctionScopedVariable = 42;
}

console.log(myFunctionScopedVariable); ReferenceError: myFunctionScopedVariable is not defined
console.log(i); // Uncaught ReferenceError: i is not defined
}

test();

Sanity restored. This is most certainly what I wanted, and definitely what I meant.

And it’s not only for functions and loops; this is the behaviour for all blocks. This code explains it better:

if (true) {
let a = 1;
}

console.log(a); // ReferenceError: a is not defined

{
let b = 2;
}

console.log(b); // ReferenceError: b is not defined;

 

But there is also another difference I briefly mentioned earlier, and that is that let “pretends” not to be hoisted.

It actually is hoisted, but if you try to use it before it’s actual declaration, an error will be thrown.

The space between the start of the block, and the declaration is called the “Temporal Dead Zone”. Don’t try to use your variables in that zone.

Allow me to demonstrate:

a = 1; // This works
b = 2; // Error: b is not defined

var a;
let b;

I personally think this is perfectly reasonable. But you have to keep it in mind, especially if you are refactoring code.

There is one more example I would like to point out, and that is a special case with let, that you might want to know about.

for (var i = 0; i < 3; i++) {
console.log("Inside the loop: " + i);
setTimeout(function () {
console.log("Inside the timeout: " + i)
});
}

This will result in the following output:

Inside the loop: 0
Inside the loop: 1
Inside the loop: 2
Inside the timeout: 3
Inside the timeout: 3
Inside the timeout: 3

Well… That is unfortunate. It kind of makes sense, but given the powerful closure patterns used in JavaScript, this kind of breaks the expectations, in my opinion.

You wouldn’t really think that let would fix this, but it turns out that it does!

The ES6 spec says that let in a for loop is scoped not only to the loop, but to each iteration.

Which means that this code:

for (let i = 0; i < 3; i++) {
console.log("Inside the loop: " + i);
setTimeout(function () {
console.log("Inside the timeout: " + i)
});
}

will result in the following output:

Inside the loop: 0
Inside the loop: 1
Inside the loop: 2
Inside the timeout: 0
Inside the timeout: 1
Inside the timeout: 2

Again, sanity resorted.

 

The const keyword

Now that we know a lot about var and let, let’s take a look at the const keyword.

 

The difference between let and const is not too big.

In fact, all the differences between var and let are also true for var and const.

In other words, let and const is almost the same. They are both block scoped and work the same way.

The only thing that makes const different is that is a constant:

A variable created with const cannot be changed after it is initialized.

And just like constants in most languages (as far as I know…), you have to initialize it when you declare it.

const a; // SyntaxError: Missing initializer in const declaration
const b = 42;
b = 43; // TypeError: Assignment to constant variable.

This is good, but you have to keep one thing in mind:

Objects in a const variable are not immutable.

That means that you can still do this:

const obj = {
ultimateQuestionOf: "Life, the Universe, and Everything",
answer: 42
};

obj.answer = 43; // This is OK, obj is not immutable
console.log(obj.answer); // 43

Which is fine, and also makes the const something that can be used in almost all cases.

 

We now know how var, let and const work. We also know how they differ from each other.

It’s time to figure out when exactly we should use the different ones.

 

When to use var

I would say: almost never.

And I really mean that. I see very few good reasons why you might want to use var.

It would have to be because you really want function scoped variables. For the most part, that’s absolutely not necessary, and is only confusing.

let and const gives you all the functionality you actually need from var, but with none of the weird edge cases you really wouldn’t expect (unless you are already a complete JavaScript expert).

 

When to use let and const

I grouped these two together, because their usage is dependent on when you wouldn’t use the other.

Any variable that you need to redefine later should use let, and any other variable should for the most part be const. If you make a mistake, most modern tools will give you a heads up anyways.

IntelliJ giving a heads up

 

Summary

You should stop using var as soon as you can.

There are few good reasons to ever use it, if you have access to let and const.

 

I also think you should use const whenever you can (whenever you don’t need to reassign a variable).

const should be your default choice, only using let when you really have to.

It makes your code clearer and more explicit about it’s variables.

 

If you have any questions, comments or constructive criticism (I’d love to get my views challenged with a good discussion), reach out to me on twitter or in the comments.

You can find me on twitter here: gjermundbjaanes


Follow me on Twitter: @gjermundbjaanes