Until JavaScript ES6 arrived in 2015 there was only one way to declare variables: with the var keyword. Now there are two more options: let and const. In this tutorial we will review all three approaches and discuss when each is appropriate.

The var keyword

The most important thing to know about the var keyword is that it is function scoped. Scope refers to how a computer keeps track of all the variables in a program, specifically the environment in which each variable is accessible. Typically we talk about scope as either being global and therefore available everywhere or local and reserved for a specific block of code.

When using var our local scope is within a function. This means that a variable with the same name can be used in two separate functions as each has their own local scope. In fact, we can use the same variable three times if we also include it in the global scope.

var greeting = "Hi from global scope!";

function sayHi() {
  var greeting = "Good morning";
  console.log(greeting);
}

function sayBye() {
  var greeting = "Goodbye";
  console.log(greeting);
}

greeting; // "Hi from global scope"
sayHi(); // "Good morning"
sayBye(); // "Goodbye"

Note that since lexical scoping means scopes can be nested, if a local variable can’t be found in the local scope, the JavaScript interpreter will move up a level to the global scope to look for it.

var color = "red";

function myColor() {
  console.log(color);
}

function myName() {
  console.log(name);
}

myColor(); // "red"
myName(); // ReferenceError: name is not defined

Here the function myColor is able to access the global variable color, however the function myName throws an error because we have not defined a variable name either locally within the function or globally.

The function-based nature of var also leads to JavaScript behavior such as hoisting, where variable and function declarations are “hoisted” to the top of the scope before assignments.

The let Keyword

In contrast to var, the let keyword is block-scoped which means that its scope is the nearest enclosing block.

{
  var x = "Ada";
  let y = "Lovelace";
}

console.log(x); // "Ada"
console.log(y); // ReferenceError: y is not defined

let: function block

Within a normal function block var and let will behave the same if the variables are declared first:

function myExample() {
  let foo = "Hello";
  var bar = "World";
  console.log(foo + ' ' + bar);
}

myExample(); // "Hello World"

However classic “hoisting” behavior is not exhibited by let, a phrase dubbed the Temporal Dead Zone for some reason.

function myFunc() {
  console.log(greeting1);
  console.log(greeting2);
  var greeting1 = "Hello";
  let greeting2 = "World";
}

myFunc();
// undefined
// ReferenceError: greeting2 is not defined

It’s worth noting that the trope you might read online to describe this, “Let’s don’t hoist,” is not strictly speaking true. Both var and let move the variable declaration to the top of the scope but only var assigns the value undefined. You can learn more in detailed post on the Temporal Dead Zone.

let: for-loop

The most notable example of the difference between var and let occurs in the common for loop.

for (var i = 0; i < 5; i++) {
  console.log(i); // 0 1 2 3 4
}

for (let j = 0; j < 5; j++) {
  console.log(j); // 0 1 2 3 4
}

console.log(i); // 5
console.log(j); // ReferenceError: j is not defined

In the first example, var is function-scoped so creating a variable i inside of a block with var will make it available outside of the block as well. This is due to closures, which are covered in-depth here.

However in the second example using let, our variable j is only available within the for loop block. That’s why the number 0-4 will be outputted but we cannot access the variable j outside of the for loop itself.

let: global scope

Used outside a function block – and therefore in the global scope – let and var perform in a similar manner.

var a = "hi"; // global scope
let b = "there"; // global scope

However within a web browser, global variables defined with let will not be available on the global window object.

console.log(window.a); // "hi"
console.log(window.b); // undefined

This Stack Overflow thread explains the technical reasons for this distinction.

let: redeclarations

Using var in strict mode (you know you should always use strict mode, right?), you can redeclare the same variable in the same scope. For example, if we attempt this in the global scope as follows:

'use strict';
var x = 'hi';
var x = 'bye';

let y = 'hola';
let y = 'adios'; // SyntaxError: Identifier "y" has already been declared

const keyword

The const keyword performs exactly like let with one distinction: the variable binding cannot change. In other words, once you bind a variable to an object, you can’t reassign that variable.

const a = {};
a = 1; // TypeError: Assignment to constant variable

However while the binding is immutable, the variable value is definitely not constant or immutable.

const b = {};
b.myProp = 'bonjour';
console.log(b.myProp); // "bonjour"

In summary, const has nothing to do with the value of a variable only the assignment. Thus if you use const with an immutable value like a string ‘hello’ or a number ‘42’, it will perform in a “constant-like” manner. But used for mutable values (objects/arrays/functions), it’s possible to cause confusion since the values can be changed.

Conclusions

So which variable declaration should you use now that we have three options? Opinions vary. A large number JavaScript poobahs such as Mathias Bynens, Remy Sharp, Eric Elliott and others have advocated for preferring const in most cases, using let otherwise, and completely ignoring var for ES6 code.

Others, such as Kyle Simpson have advocated a more nuanced approach: start with var, use let for block scoping, only use const as a last resort for immutable values (strings/numbers).

Most likely the first approach will win out in my estimation, but at the cost of initial confusion for developers who believe const truly means a constant value not just assignment.