Should I use const, let, or var in JavaScript?

by Gordon Zhu, December 2024

In this post, you'll learn everything you need to know about const, let, and var. Along the way, you'll explore key concepts about memory and scope that will help you build a robust mental model for how everything works. At the end, you'll learn how to choose the right type of variable for any situation.

I explain everything in a way that builds on itself. So you should read the post from top-to-bottom without skipping around.


const

Variables declared with const (short for constant) cannot be reassigned. This makes const the most restrictive type of variable.

With const, primitives work as you'd expect:

const numberDeclaredWithConst = 10;

// If I try to reassign, I get an error.
// "Uncaught TypeError: Assignment to constant variable."
numberDeclaredWithConst = 15;

With const, objects work as you'd expect too, IF you understand how assignments work:

const o = {};

o = {}; // "Uncaught TypeError: Assignment to constant variable."

o.name = 'Gordon'; // But this works fine. Why?

With o = {};, we're attempting to reassign our constant o (a big no-no). So we get an error, as we'd expect, since as we mentioned already, reassignments are not allowed.

But why is o.name = 'Gordon'; allowed? Well, let's slow down and think carefully. We have a constant o, which cannot be reassigned, but with o.name = 'Gordon'; have nothing of the sort! Instead, we are assigning a value to the name property of o. Assigning a property of o is not the same thing as assigning o!

Is there a satisfying mental model for this?

Yes! Conceptually, an object is just a collection of 0 or more values. That's what distinguishes objects from primitives. Objects can contain multiple values, primitives can't.

With this in mind, I think of an object as simply a bag of stuff. Yes, think of an actual bag, like a shopping bag or backpack.

So when you have something like const bag = {}, the bag constant can't be reassigned. BUT modifying what's inside of the bag (analogous to modifying object properties, changing items in an array, etc) is totally cool! To use the formal computer science terminology, objects are mutable.

Hold up, so how do primitives fit into this mental model?

Unlike mutable objects, primitives in JavaScript are immutable (the opposite of mutable), meaning we cannot change them at all.

Say that you have const myNumber = 5;. We know that 5 can't change, because it's immutable. And so the only way to change myNumber would be to reassign it to a different immutable number. But variables declared with const can't be reassigned!

Now you have the full picture on why primitives work as you'd expect with const.


let

Variables declared with let can be reassigned. It behaves exactly like const in all other respects.

Primitives work as you'd expect:

let letNumber = 10;

// Reassignments are cool!
letNumber = 15;

Objects work as you'd expect too.

// First I'm an object.
let letObject = {};

// Now I'm an array :)
letObject = [];

Tying this back to the "bags" we discussed earlier.

When you have const bag = {};, you're saying that bag can't be reassigned to anything else. But as we saw earlier, modifying the contents of bag is fine.

In contrast, when you have let bag = {};, you're saying that bag could initially be the empty backpack sitting on your floor. But later on, bag could change to something entirely different, like the shopping bag on your kitchen counter, or even a primitive.

const and let have block scope

A block is 0 or more statements surrounded by curly braces. Blocks can stand on their own, or they can be part of if-statements, while-loops, for-loops, or functions.

{
  // This is a block with one statement.
  console.log("I'm part of a block");
}

if (true) {
  // This is an empty block.
}

function emptyFunction() {
  // An empty block in a function.
}

A variable created with let or const is accessible in the block where it was created, or any nested blocks. In the example below, I'll use const, but let works exactly same way:

{
  const numberDeclaredWithinBlock = 10;

  {
    // This works because we're in a nested block
    console.log(numberDeclaredWithinBlock); // 10
  }
}

// We'll get an error because we're OUTSIDE of the block.
// Uncaught ReferenceError: numberDeclaredWithinBlock is not defined
console.log(numberDeclaredWithinBlock);

If you declare a variable with const or let outside of any block, it will be accessible globally throughout your program.


var

Variables declared with var are similar to let, in that they can be reassigned. The crucial difference relates to scope. With let, we had to pay attention to all types of blocks. However, with var, we only have to pay attention to function blocks.

I'll modify what I said about let before so that you can see the difference:

A variable created with let or const `var` is accessible in the block function where it was created, or any nested blocks functions.

function myFunction() {
  var myName = 'Gordon';
}

// Uncaught ReferenceError: myName is not defined
console.log(myName);

If you declare a variable with var outside of any function, it will be accessible globally throughout your program.

So which type of variable should I use?

  • If you can guarantee that the variable will never be reassigned, choose const
  • Otherwise, choose let

In professional settings, there isn't a good reason to use var, but its simplicity (fewer restrictions) can make it a useful teaching tool.

The reasoning here is similar to why most teachers prefer using untyped languages with beginners. The beginning stage is the most fragile period, so anything that can reduce mental overhead is extremely valuable. After students have the basics down, they can handle more complexity.

For overachievers only

At the highest level, I really do think of objects as bags of primitives. But if you go down one level in the conceptual hierarchy, you'll quickly run into the idea of memory addresses.

How memory is stored is a core concept in all programming languages and a crucial prerequisite to working with algorithms and data structures. But it's tricky to internalize—so much so that many professional developers have a hazy understanding.

Memory addresses are beyond the scope of this article, but if you're interested, I go into exhaustive detail in my course, Programming Foundations.