var vs let vs const in JavaScript

This is part of our Modern JavaScript course. Check it out if you like this post.

In this video you’ll learn two new ways to create variables in ES6 (ES2015). They are const and let. To better do that, we’ll compare var with let and const by diving into function vs block scope, variable hoisting, and immutability.

ES2015 introduces two new ways to create variables, let and const. As you probably know by now if you’ve watched any of my videos I don’t really like to make assumptions as to what you know. With that said, before we start talking about the difference between var, let, and const, there are three things we need to talk about first. They are variable declarations vs initialization. Scope, specifically function scope, and hoisting. Now I realize you might already know those three things so if you do, feel free to fast forward. But for now, let’s talk about variable declarations vs initializations.

A variable declaration introduces a new identifier. Here, we create a new identifier called declaration. In JavaScript, variables are initialized with the value of undefined when they are created. So if we log the declaration variable, we get undefined. Initialization is when you first assign a value, to a variable. So here, we’re initializing the declaration variable, by assigning it to a string. This leads us to our second concept, scope.

Scope defines where variables and functions are accessible inside of your program. In JavaScript, there are two kinds of scope - global scope, and function scope. According to the official spec, “If the variable statement occurs inside a FunctionDeclaration, the variables are defined with function-local scope in that function. Otherwise, they are defined with global scope, that is, they are created as members of the global object”. What that means is if you create a variable with var, that variable is scoped to the function it was created in and is only accessible inside of that function or, any nested functions. If you create a variable without using var, that variable is created as a property on the global object. So let’s take at what this looks like. Say we had a function called getDate and it’s honestly going to be a pretty basic function. Say we had a date here and then now let’s go ahead and return date. So what we’ve done is we’ve created a variable called date inside of our getDate function. As you probably know, we can invoke getDate to give us the date, but, if we try to access date from outside of this function, we’ll get a reference error because we created the date variable inside of this function so only this function as well as any functions inside of it can access date. For example, if we created another function in here for some reason, we’ll call it formatDate, and then what we’ll do, is we will return date.toString and then we’ll format it a little bit. So now instead of returning date, we can return the invocation of calling formatDate. And notice here that our formatDate function, because it’s nested inside of our getDate function, still has access to the date variable, but outside of the function we do not have access to it because date is scoped to this getDate function. Now I realize this is a pretty basic example so let’s go ahead and take a look at a more complex example.

So let’s create a function here, we’ll call it discountPrices. This function is going to take in a prices array as well as a discount. And then what it’s going to do is it’s going to loop over the prices array, and for each item in that prices array, we’re going to discount it and then we’ll return this brand new array full of our discounted prices. So we’ll create a for loop here, and for each price in our prices array, we will create a discountedPrice variable which is going to be the specific price times one minus the discount that was passed in. Then we will create a final price which is just going to use Math.round so that way we don’t have any floating point errors. And then once we have that final price, we’ll go ahead and push that into our discounted array. So now the last thing to do is just return discount. So if we were the invoke this function, let’s just say we passed in an array of 100, 200, and 300, and a discount of .5, what we would get back is a brand new array with all of those values halved. So now to better demonstrate that variables declared with var are function scoped let’s go ahead and console.log a few items right here. So first, let’s go ahead and console.log i, and then we will console.log the discountedPrice variable and lastly, we’ll console.log finalPrice. So if we grab this, then we will head over to our console let’s go ahead and run this. You’ll notice here that what we got was 3 for i, 150 for discountedPrice, and 150 for finalPrice. Now if JavaScript is the only language that you know, you’re probably not too confused or too surprised about what’s going on here. But, if you’re coming to JavaScript from a different programming language, specifically, another language which is block scoped, you’re probably a little bit concerned about what’s going on here. This isn’t really broken, it’s kind of just weird. You’ll notice that we still have access to our i variable even though we are outside of this for loop. In fact we still have access to all of our variables, even though there’s really no reason to have access to any of these variables outside of this for loop. It doesn’t really do us any good, and eventually it might even cause us some harm. But, because variables declared with the var keyword are function scoped, again, these variables right here, can be accessed anywhere inside of this function.

Now that we’ve discussed declarations, initializations, and scope, the last thing before we can dive into let and const is hoisting. When the JavaScript interpreter evaluates your code, it will move all function and variable declarations to the top of the current scope. This is referred to as hoisting. So when you write something that looks like this, the interpreter will change it into this. Why does this happen? I don’t really know, and honestly I don’t really care. But I do care that it does happen, and I do care that I know what’s going on when it does.

Let’s take a look at the previous example, and see how hoisting affects it. Here we have our discountPrices function that we created earlier. So what’s going to happen when the JavaScript interpreter evaluates this code, is it’s going to take all of our variable declarations and hoist them to the top of this function. So, once interpreted, all of this code is going to change from this, to this. With all of our variable declarations being hoisted, and now, because all of our variables have already been declared, if we want to reference any variables, we simply reference them by their identifier. Now as a quick side note, you may have remember earlier when I read the specification about declaring variables, I said that if you don’t declare a variable with the var keyword, then that will become a property on the global scope. And the reason for that is because of hoisting. So again what happens, our variable declarations are hoisted, so now, when we have a variable discounted it looks inside of the scope for a declaration of the discounted variable. If it doesn’t find it, it will go to its parent scope, and look for a declaration of discounted, and it will do that all the way until it gets to the global scope. So if we go back to our original code, if now, we leave off this var keyword, what’s going to happen is when the JavaScript interpreter evaluates this code now, it’s going to change it to this, then, it will look for a discounted declaration inside of this function. It won’t find one, because we left off the var keyword, so then it will go to the parent, and it’ll repeat that process all the way until it gets to the global scope and then once it doesn’t find a discounted declaration, it will add discounted as a property to the global scope. So that’s why you should never declare a variable without a var keyword, because if you do that, whatever that variables name is will become a property on the global scope, which is a terrible idea.

Now that we know everything there is to know about var, let’s finally talk about the whole point of why you’re here. How does var, compare to let or const? First, let’s compare var and let. The main difference between var and let is that instead of being function scoped, let is block scoped. What that means is that a variable created with the let keyword is available inside the block that it was created in as well as any nested blocks. When I say “blocks, I mean anything surrounded by a curly brace like in a for loop or an if statement. So now looking back to our discountPrices function one last time. You’ll remember that when we console.log these variables, what we got were 3, 150, and 150. That’s because we declared the variables with the var keyword which meant that they were function scoped. Meaning, they were able to be accessed inside of this function. But now what happens, is let’s come in here and let’s change all of them to let. And I will copy. Now when we go to run this, what’s going to happen, is you’ll notice that we get a reference error. i is not defined. So right here we are trying to access i but, because we declared i with the let keyword, instead of var, that means i is only scoped to this specific block right here. To show you this, let’s go ahead and change this to var. Now if I run this, you’ll notice I get discountedPrice is not defined because again discountedPrice was declared with the let keyword, which means it’s only accessible in this block right here. The next difference has to do with hoisting.

As we saw with var, referencing a variable before the variable is declared will give you a value of undefined. This isn’t very intuitive, as we probably shouldn’t be referencing variable before they’re declared anyway. Luckily for us, let fixes this. Variable declared with the let keyword, will hoist the variable declaration to the top of the block just like with var, but, referencing the variable before the declaration will result in a reference error. To see this in action, let’s revisit our hoisting example we saw earlier with var. Even though we’re logging the hoisted variable before the variable is declared, because of hoisting, the interpreter will modify our code to look like this. So when the hoisted variable is logged, we get undefined. However, with let, the interpreter will still hoist the variable declaration but unlike var, if you try to reference a variable before the declaration, you’ll get a reference error.

So to recap so far, unlike var, variables declared with let will be block scoped and, trying to reference them before they’re declared will throw a reference error. Now that we understand the difference between var and let, what about const? Turns out, const is almost exactly the same as let. The only difference is that once you’ve assigned a value to a variable using const, you can’t reassign it to a new value. Let’s dive a little bit deeper into const because there are some common misconceptions.

We’re going to make two variables. The first one we’ll call name, and we will use the let keyword. The second we will use const and we will call it handle. Both of them will be strings. Now, if I wanted to reassign a new value to name, I can do that just by like you’re used to, saying name = ‘Tyler McGinnis’. But, if I try to reassign a new value to handle, what’s going to happen is we’ll get this error right here saying “uncaught type error: Assignment to constant variable”. So with const, once you’ve assigned a variable a value, you can’t reassign it a new value. Notice I’m being pretty clear with my words when it comes to const. We can’t reassign a new value to const. This is where it gets a little bit tricky. Say we had a person object that we declared with the const keyword. We’ll just give it a name property. Notice, what I can do, I can come in here and say = ‘Tyler’. Because what I’m not doing, is I’m not reassigning person a new value. Instead, I’m just modifying an existing property on the person object. So if we look at our person object now, instead of being “Tyler McGinnis”, it’s just “Tyler”. But what I can’t do, is I can’t come in here and say person now equals a new object, because, since we declared it with the const keyword, we can’t reassign it a new value. So that’s why you need to be careful with const because const variable aren’t immutable. You can still change them. They just can’t be reassigned a new value.

Now the last question we need to answer, is which one should we use. The popular opinion, and the opinion that I subscribe to is that you shouldn’t always use const unless the variable is going to change. The reason for this is by using const, you’re telling your future self as well as any other future developers that use your code that this variable shouldn’t change. If it does change, you should use let, like in a for loop. So between variables that change change and variables that don’t change, there’s really no other use cases so that means we really never need to use var again. Now the unpopular opinion, though it still has some validity to it, is that you should never use const, because, when you use const, what you’re trying to do is you’re trying to say “this variable will never change”. Unfortunately, if that variable is an object as we saw earlier, it can still change.

So to recap, var is function scoped and if you try to use a variable declared with var before the actual declaration, you’ll just get undefined. const and let are blocked scoped and if you try to use a const or let variable before the declaration you’ll get a reference error. And the difference between let and const is that once you’ve assigned a value to const, you can’t reassign it, but with let, you can.

Liked this post? Share it 🕺

This is part of our Modern JavaScript course. Check it out if you like this post.