The Difference Between var, let, and const in JavaScript

Variable declaration keywords in JavaScript can seem like the Spider-Man meme.
TL;DR
  • JavaScript has a unique history which has contributed to its confusing features and structure.
  • When considering when to use var, let, or const, you must take into consideration scope, hoisting, redeclaration, and other environmental factors.
  • I prefer to use const for constant values and let for everything else.

Programmers of All Backgrounds Find JavaScript Confusing

Photo by 愚木混株 cdd20 on Unsplash

Without any prior knowledge of JavaScript, doing something as simple as declaring variables can be confusing. Or at least that’s what I found out when I was doing some internal consulting for another department of my old company. I was working with a seasoned pro, someone whose email signature read “Principal Consultant” and who was responsible for architecting entire, full-stack, standalone solutions for external clients that were required to seamlessly integrate with our existing products.

We would have occasional calls to discuss aspects of the project, and we’d often go back and forth via email. Then one day, I popped open my email client and saw an email from him. I liked to tackle his questions first thing in the morning because he lived on the East Coast, and I wanted to make sure I wasn’t blocking him from getting his work done. I read his email and eventually got to a section that read, “What’s the difference between let, const, and var?” I was initially taken aback a bit because such an elementary question to be asked by an engineer with probably twice the experience I had was shocking.

Then I realized this actually makes a lot of sense. JavaScript is confusing, and to have three different ways to declare variables can seem excessive (and it probably is). This pattern was only recently mainstream in the last 5 or so years with ES6, and for someone who hasn’t actively developed in JavaScript recently, the changes from ES6 would make JavaScript seem like an entirely different language from the versions that preceded it. JavaScript has a complicated history, so to understand “why” things are done the way they are, it would be helpful to understand how we got here.

A Brief History of JavaScript

Photo by Natalia Yakovleva on Unsplash

The early web connected people in a way that was only possible in sci-fi films, previously. With that said, it was comprised of slow-loading, static pages that would be unrecognizable today. In 1993, the Mosaic web browser was created, and it allowed users to view web pages with their photos inline with the text (as opposed to in another window).

The lead developers for Mosaic founded Netscape, which introduced the incredibly popular Netscape Navigator. They started working on adding support for a scripting language, which they named JavaScript to ride the coattails of the hot programming language of the time: Java.

Microsoft entered the scene and introduced the Internet Explorer web browser in 1995 and effectively started the “browser wars.” Both web browsers began to support their own scripting languages to provide a richer, more dynamic web browsing experience to the end-users.

Having two main browsers with two separate scripting languages meant that web pages would often only fully function in one browser or the other. This fragmentation lead to the ECMAScript standardization in 1997. With the explosive growth of the internet, ECMAScript (i.e. JavaScript) became the defacto browser language and grew to incredible popularity.

Spotlight Because of its complicated history and its ongoing evolution, JavaScript remains one of the more popular but confusing programming languages to learn.

Being commissioned with being a flexible scripting language while also being born in the shadows of Java made JavaScript a uniquely confusing language. JavaScript is weakly and dynamically typed but you can use the TypeScript superset of JavaScript to add strict typing. You can easily use both the Object-Oriented Programming (OOP) and Functional Programming paradigms in the same application. Because of its complicated history and its ongoing evolution, JavaScript remains one of the more popular but confusing programming languages to learn.

The var keyword

The var keyword is the oldest of the three keywords, and thus, it can be considered a little outdated. It has some odd behavior that can produce bugs without the proper knowledge or development environment configuration.

Scope

A variable declared using the var keyword is function scoped, unless declared outside of a function, then it is globally scoped. Take a look at the illustration below to understand function scoping.

var declarations are scoped to their immediate containing function

The var variable above is scoped to the foo function, and therefore, it is not “visible” to the global scope around it.

Hoisting

var declarations are hoisted and initially undefined before eventually being assigned. The JavaScript interpreter will hoist the declarations of function, variables, and classes and initialize them as undefined (with a few exceptions— keep reading). Thus, we can run the following code below without throwing an exception.

var declarations are hoisted and initialized to undefined

This might be a little confusing, so let’s explain what the JavaScript interpreter is actually doing. Before executing your JavaScript code, the interpreter will scan through and create a mapping of your declarations to optimize the code execution. For all of these declarations, the interpreter doesn’t assign the proper values because this would require executing code. Instead, it creates the mapping with all of these declarations assigned to undefined (again, with a few exceptions). Let’s illustrate this concept in code:

var declarations hoisting can be visualized as undefined, initially

Declaration

The var declaration can be re-declared. This is a subtle but powerfully dangerous concept that should not be overlooked because this feature allows you to introduce naming collisions into your code. Take a look at the example below:

An example of a naming collision using the var keyword

This is a trivial example, but you can imagine how easy it would be to accidentally redeclare a variable in a much larger codebase. Modern development environments will allow you to configure a “linter” to warn you when you may have accidentally reassigned an existing variable.

The let keyword

The keyword let was introduced in ES6, and as this latest ECMAScript standard became widely adopted, let started to overtake var as the go-to keyword to declare variables. There are some main differences that make let a little less permissive, and therefore “safer”, than var.

Scope

let is block-scoped, which is to say, it is scoped to its nearest containing block. So instead of only being scoped to the containing function block like var declarations, let declarations are scoped to the first containing block of all varieties. A block statement in JavaScript is any code in between a valid pair of “curly braces” (i.e. { and }). So, this includes if statements, for and while loops, function declarations, and even a standalone set of curly braces among other use cases. Let’s illustrate what I’m talking about here:

let declarations are block-scoped

Hoisting

While all declarations are hoisted, let declarations behave a little differently than the hoisting described previously for the var keyword. Even though let declarations are hoisted, they are not initialized to undefined like hoisted var declarations are. This means that you can’t reference a variable that is declared using the let keyword before it is initialized.

let declarations are hoisted but not initialized to undefined

This scenario illustrates a quirk that is referred to as the “Temporal Dead Zone (TDZ).” This is a temporal issue because it has to do with timing. Since the variable is not initialized until its first assignment during runtime, an error will be thrown if you try to reference it prematurely.

Declaration

Another way the let keyword is less permissive than var is that it can’t be redeclared. Redeclaring in general is a bit of an odd feature of JavaScript, and designing it out of the let keyword was a major win. Here’s an example of what I’m talking about.

Redeclarations of let variables are not allowed

To be clear, redeclaration and reassignment are two different things. You can definitely reassign let variables as you normally would expect. They wouldn’t be very good variables if they couldn’t vary.

You can still reassign let variables

The const keyword

The const keyword is yet another way to declare variables. As the name would imply, const declarations can be effectively referred to as “constants.” Let’s break down their behavior.

Scope

Much like the let keyword, const declarations are block-scoped.

Hoisting

Hoisting for the const keyword behaves exactly the same way as the let keyword. It is hoisted but uninitialized, and therefore, it will experience the same Temporal Dead Zone issues.

Declaration

You can’t redeclare a const declaration once you’ve already declared it. Again, this is the exact same behavior as with let declarations. However, because lets and consts are block-scoped, it may sometimes seem like they are being redeclared when they actually aren’t. Take a look at this example:

Block scoping can create the illusion of redeclaration

You’ll notice that we declare and assign the foo constant twice, but the execution of our code will not throw an exception. Again, this is because of the block scoping nature of const declarations.

Assignment

The main feature of the const keyword is that it can’t be reassigned. Therefore, you must immediately assign a value to the constant, otherwise, that constant will remain undefined. One notable “loophole” can be seen below:

A “loophole” to change the value of a constant

Even though foo is a constant, the code above does not generate an exception. Can you tell what’s going on here? Take a second to think about it.

Although it appears that we’re violating the primary feature of the const keyword, strictly speaking, we aren’t actually reassigning the foo constant. This is because we are using the Object data structure. So we initialize foo to an Object instance, and at every step of the way, foo is always referencing that same Object instance. We are simply adding another property to that object instead of reassigning foo.

When to Use Them

You should use whatever you feel comfortable with, but I like to only be as permissive as is required. If your value will never need to be reassigned, use const, otherwise, use a let. I never use vars anymore because they are too permissive. If I ever need to reference a variable outside of its scope, I simply move the variable declaration to a higher, common scope. I’ve also never had a reason to take advantage of var’s lack of a Temporal Dead Zone, and in fact, I see that attribute as more of a bug and not a feature.

Conclusion

Even experienced developers can find JavaScript confusing at times. Its unusual history makes it one of the most popular languages today, but also one of the most difficult to master. Developing a thorough understanding of var, let, and const is an important step to building a strong JavaScript foundation.

Michael has a diverse background in software engineering, mechanical engineering, economics, and management. After a successful career in mechanical engineering, Michael decided to become a software engineer and attended the Hack Reactor coding bootcamp. Michael enjoys writing about his experiences and helping others start their journies in software development.