When I first started learning Javascript I had a hard time wrapping my head around when to use the var
, let
, and const
keywords.
I was used to writing native iOS apps in Swift (and Objective-C when push came to shove). In Swift, the appropriately named var
is used to create re-assignable variables. The not-so-appropriately named let
is used to create constants. And then there’s the static
and class
keywords used to create typed properties.
As you’ll read below, this has almost nothing to do with the way we do things in JavaScript ES6, which was initially confusing to me. My goal for this post is to demystify the use of var
, let
, and const
to help you understand when you should use which.
Before we dive in, it’s important that you understand the following terms:
- Scope
- Hoisting
- Assignment
Don’t worry if they’re unfamiliar, I’ll explain what they are and how they tie in along the way.
One of the biggest differences between var
, let
, and const
comes down to their scope.
In computer programming, the scope is a term used to tell us how accessible an object (or variable) is. Think about it as a perimeter drawn around that variable where only the code that is within that perimeter can access that variable. The “narrower” the scope, the more restrictive it is to access.
In ES6, a var
can have two types of scope: a global scope, and a function scope.
A var
with global scope is accessible throughout your entire JavaScript code. If we continue with our above analogy, we could say that its perimeter is the window object in which your entire code is running.
A var
with a function, the scope is only accessible within its enclosing function. We could say that its perimeter is limited to the function that declares it.
// Variable a has a global scope var a = 'hello'; function doSomething() { // Variable b has a function scope var b = "what's up"; console.log(a); // The console prints: hello console.log(b); // The console prints: what's up } console.log(a); // The console prints: hello console.log(b); // ❌error: Uncaught ReferenceError: b is not defined
If we were to run this code, the compiler would have no problem console logging a
inside or outside the function since a
is declared at the top of the file with a global scope. b
is declared inside our doSomething()
function so it’s accessible within the function but the compiler throws an error when we try to access b
outside the function since it’s not globally scoped.
Seems pretty straight forward and looks a lot like Swift (minus the incessant semi-colons everywhere 🥺).
But we’re talking about JavaScript here people, it doesn’t end there. Take a look at what happens when we add a for
loop into the mix…
var a = 'hello'; function doSomething() { var b = "what's up"; console.log(b); for (var i = 0; i < 5; i++) { // Do stuff that doesn't matter for this example... } // 😳Wait what? Even though variable i was declared in the loop... console.log(i); // ...and we're done with the loop, the console prints: 5 } console.log(a); console.log(b);
As you can see, our variable i
which was declared and initialized within the context of the for
loop is still accessible AFTER THE LOOP HAS FINISHED EXECUTING!
At first this made no sense to me. But as a I read the docs, I understood that this behavior is allowed because var
is function-scoped. This is where hoisting comes in, I’ll explain.
In Javascript, hoisting means that variable declarations are processed before any code is executed. This means that the compiler will first identify all the variables that are declared within a scope before it goes on to execute the rest of the code and will assign to them a default value of undefined
.
In doing so it’s as if you’ve declared all of your variables at the top of the scope. To the compiler, our code above looks more like this:
var a = 'hello'; function doSomething() { var b; // Initialized with the default value of: undefined var i; // Initialized with the default value of: undefined var b = "what's up"; console.log(b); for (var i = 0; i < 5; i++) { // Do stuff that doesn't matter for this example... } console.log(i); } console.log(a); console.log(b);
As you can see, all vars
declared within the function (b
and i
) are first declared at the top of the function’s body. Why is it called hoisting then? Think of the vars
inside the scope as a flag. The compiler takes those variables (which make up the flag) and hoists them up to the top of the scope, much like you would hoist up a flag to the top of a flagpole.
So since these variables are actually implicitly declared by the compiler at the beginning of the function it makes sense that we can access them anywhere within the function, including after the for
loop.
Now where does that leave us with var
, let
, and const
?
let
only has one scope: the block scope. Contrary tovar
, it’s limited in scope to the block, statement, or expression on which it is used.
Going back to our perimeter analogy, we can say that its scope perimeter is drawn by the curly braces {}
that surround it. For example, an if
statement or a for
loop would create a new block scope.
So if we take our code example and replace our var
with a let
in the for
loop, we get this:
var a = 'hello'; function doSomething() { var b = "what's up"; console.log(b); for (let i = 0; i < 5; i++) { // Do stuff that doesn't matter for this example... } console.log(i); // ❌error: Uncaught ReferenceError: i is not defined } console.log(a); console.log(b);
Since let
is block-scoped, and i
is declared and initialized by the for
loop, if we try to console log i
after the loop has finished executing, we’ll get an error. I like this behavior much better since it’s more like other languages I’m used to and is less confusing to think about. In programming (and actually most of life), less confusing means less errors.
Now where does that leave const
? let
and const
are similar in that const
can be block-scoped so all the above rules that we outlined for let
also apply to const
.
The difference between the two lies in part with their assignment.Assignment is the last term I mentioned was important to grasp at the beginning of this post, you’ll see why. Assignment is simply giving a value to a variable.
// Here we're assigning a value of 5 to our "number" variable. let number = 5;
With that in mind, re-assignment simply means that you can assign a new value to an existing variable.
// Here we're assigning a value of 5 to our "number" variable. let number = 5;
// And now we're re-assigning a value of 10. number = 10;
Re-assigning is completely allowed withlet
, but absolutely illegal withconst
. In case you didn’t already guess,const
was appropriately named after the term “constant”, which is a value that doesn’t change.
For example, this would throw an error:
const a = 'hello';
a = 'hello there!'; // ❌error: Uncaught Error: "a" is read-only
Another difference is that a const
can be declared globally in your file, like a var
, while a let
can only be block-scoped.
This table summarizes what you’ve learned so far:
That’s all fine and dandy, but when should you use which keyword when you’re writing code?
It’s pretty much been accepted across the board that var
is a thing of the past. You may stumble upon old code that still uses it but if you’re writing new code, it’s best not to use var
.
The reason why is that it’s easier to make mistakes related to scope with var
. Take this example:
function doSomething() { var number = 5; if (1 > 0) { var number = 1000; console.log(number); // The console prints: 1000 } console.log(number) // 🤨The console prints: 1000 }
We initialize a variable at the beginning of our function. Later, in an if
statement, we think we’re declaring a new variable limited in scope to the statement, but since var
is function-scoped we’re actually inadvertently redeclaring our initial variable that has the same name.
This may seem easy to avoid since our example is really simplified, but imagine having to deal with a much more complex function with different nested blocks and statements or better yet, having to work with someone else’s code that you aren’t yet familiar with. Things can get messy real fast.
Because let
and const
have better scope management, it’s advised to use them instead of var
. That’s right, you can just go ahead and forget about var
when you’re writing new code 🤠
Now deciding between let
and const
pretty much just comes down to whether you want to be able to re-assign your variable or not. You can think about this in two ways.
You can decide to always use const
unless you know that the value is going to change.
Or always use let
unless you’re sure that you are going to be dealing with a constant.
So there you have it. You should now be better equipped to choose when to use var
, let
or const
when writing JavaScript. I hope this blog post has cleared things up for you.
I would add that whenever you’re trying to learn a new topic in programming, you should try and take a look at the docs written on that topic by a trusted source. I like to look through Mozilla’s MDN Web Docs. They provide thorough explanations for all the terms and topics that you could think of in Javascript and other web languages.