Differences Between var, let, and const | Hoist and Scope

Why are there so many variable declaration keywords in JavaScript? Let's take a look at each one, and also dive into the concepts of hoisting and scope.

Keywords: javascript, coding tutorial, hoisting, scope

By Carmen Cincotti  

Whether you are a budding JavaScript developer, or an expert looking to really learn the intricacies of the language - one question may stump you:

var, let, const - what are the differences between these three keywords in JavaScript?

Confused woman

Some JavaScript developers stick to a golden rule to use const unless a variable is expected to be reassigned in the future. In that case, they use let.

const all the things

However, sometimes that’s not always the case, and you might encounter code that only uses var.

Let’s get started!

Scope

To begin our investigation into the world of JS syntax, we’ll first need to learn a little about scope.

Scope defines the accessibility of variables during the lifetime of a program.

Regarding JavaScript, there’s global scope, function scope, and block scope:

  • Global scope - scope accessible anywhere throughout the program.
  • Function scope - scope within a function definition.
  • Block scope - scope in-between { } like in the body of a for-loop.

Take for example the following example:

// Global scope const globalScopeVariable = "global" function functionScope() { const functionScopeVariable = "function" // Block scope { const blockScopeVariable = "block" // can access globalScopeVariable, functionScopeVariable and blockScopeVariable } // can access globalScopeVariable, functionScopeVariable } // can access globalScopeVariable

As we can see, the scope controls the accessibility of certain variables. We say that a certain variable is “scoped” to a certain type of scope (i.e. blockScopeVariable is scoped to the block that it finds itself in).

The relationship between the accessibility of variables and scope can operate somewhat like a Russian doll.

Picture of Russian doll

For example, global scope encapsulates both function and block scope. Function and block scope work somewhat interchangeably.

For example - this is perfectly OK JavaScript:

function scopeOne() { { function scopeTwo() { { let high = "five" } } } }

As you can see - the function scopeTwo is defined within a block.

Now that we have an understanding of scope - let’s take a look at the differences of var and let.

var vs let - Hoisting

Let’s start by defining two variables. One with the keyword let and the other with the keyword var:

let myLet = "Apple" var myVar = "Pie" console.log({ myLet, myVar }) // OUTPUT: { myLet: 'Apple', myVar: 'Pie' } // **Protip** - logging variables as an object increases readability, because key-value pairs are logged to the console.

Now let’s create a function. By doing this, we’ll show a key difference by logging these two variables to the console before they are defined.

function myFunction() { console.log({myVar}) console.log({myLet}) let myLet = "Apple" var myVar = "Pie" } myFunction();

Running this yields us the following error:

{ myVar: undefined } Thrown: ReferenceError: Cannot access 'myLet' before initialization at myFunction

…What? 🤔 myVar is undefined, and myLet returns a ReferenceError that states that myLet has yet to be defined.

What’s happening here?

myVar is hoisted to the top of the function scope, and thus is considered accessible throughout the function even before it is defined with a proper value. In fact, it takes the value of undefined.

Before continuing on, let’s take a look at how our JavaScript code is built and ran, and from this we will better understand this concept of hoisting.

Creation and Execution of JavaScript Code

When interpreting JavaScript code, there are 2 phases.

⚠️ This is a generalization. Each JS engine is different.

  1. “The Creation Phase” is when the JavaScript interpreter allocates space for variable and function declarations in memory.
  2. “The Execution Phase” is when the JavaScript interpreter starts executing our code line by line.

So what exactly happened during “the Creation Phase” in the example snippet from above? myVar was defined a default value of undefined.

myLet is different. It is allocated memory, and thus technically hoisted but is considered to be in a state named the Temporal Dead Zone. In this state, myLet is allocated memory, but this memory is technically inaccessible.

So to summarize our first difference between let and var:

var can be accessed and overwritten anywhere within a function scope or global scope due to hoisting. let also exhibits this behavior, except it cannot be accessed due to being in a state named the Temporal Dead Zone.

var vs let - Scoping differences

Another difference is behavior of let and var in regard to scope. Let’s take a look at this example:

var myVar = "FirstVar" let myLet = "FirstLet" function myFunction() { console.log({myVar}) console.log({myLet}) { console.log({myVar}) console.log({myLet}) var myVar = "SecondVar" let myLet = "SecondLet" } } myFunction();

Which returns to us the following:

{ myVar: undefined } { myLet: 'FirstLet' } { myVar: undefined } Uncaught ReferenceError: Cannot access 'myLet' before initialization at myFunction

…Uh what? 🤔 myVar is undefined and myLet is FirstLet, then it become?

It looks like myLet inherited the value FirstLet from the global scope, and it became inaccessible when it was reassigned within that block.

It might not be obvious, but the difference we see here is that the var defined variable, myVar, is hoisted within the nearest function definition of which it resides, while let does not do this. let is hoisted to the nearest block.

This is why we are returned that myLet is inaccessible within the block scope, but not within the function scope!

To summarize: var is function-scoped and let is block-scoped!

Summary of let/const vs var

So in summary the two main differences between let (as well as const) and var are:

  1. How each are initialized in the ‘Creation Phase’
    • var is initialized with undefined
    • let/const are initialized as Temporal Dead Zones and thus are considered not accessible until they are defined in the Execution Phase.
  2. How each are scoped
    • var is function-scoped.
    • let/const are block-scoped.

Bonus: an additional word on hoisting

Functions can also be hoisted. Let’s take this example:

catName("Chloe"); function catName(name) { console.log("My cat's name is " + name); } // OUTPUT: "My cat's name is Chloe"

This function declaration is not like what we saw with var where it returns undefined. In fact, it is technically accessible when we call it before in the code!

In any case, if you find that this is annoying - you can add strict mode to the top of your JavaScript code to disable hoisting altogether:

'use strict';

Resources


Comments for Differences Between var, let, and const | Hoist and Scope



Written by Carmen Cincotti, computer graphics enthusiast, language learner, and improv actor currently living in San Francisco, CA.  Follow @CarmenCincotti

Contribute

Interested in contributing to Carmen's Graphics Blog? Click here for details!