Hajime, the duck guy

Tuesday, April 16, 2024, by Hajime Yamasaki Vukelic

Const doesn't make things immutable.

If you believe otherwise you're mislead, lied to, scammed, taken for a ride...

The proof

Anyway, here's a small snippet of code:

const foo = []
foo[0] = 1
console.log(foo)

If you run this in your console, you should see that I was able to mutate the array even though it's assigned to a const variable. End of story. Well, not really, but that proves const didn't make the array immutable after all.

What's real immutable?

What const does is it prevents me from reassigning the name foo. That's all it does. It doesn't do anything to the value itself.

What about this next snippet:

const foo = Object.freeze([])
foo[0] = 1
console.log(foo)

Now you see I was not able to modify the array. The array is still empty even though I assigned a value to its first slot. Now this is properly immutable. In fact, I can use a var to declare foo and it won't change the fact that the array is immutable:

var foo = Object.freeze([])
foo[0] = 1
console.log(foo)

If you enable strict mode by adding 'use strict' to your module or function, you'll see that the above code will actually throw an exception saying I cannot create a property 0 on an object that isn't extensible.

If you look at languages that don't even have mutable data structures like arrays (e.g., Elm), you'll notice that the way you work with those structures is a bit different. In JavaScript speak, you're talking about something along these lines:

const foo = Object.freeze([])
const fooWithOne = Object.freeze([...foo, 1])

What does this do? It creates a completely new array that has everything in foo and adds 1 to the end. This array is unrelated to the previous one except that it contains the same elements.

Performance issues

If you're no longer using the original array after that, it gets garbage-collected. This is important. It gets garbage collected, and garbage collection is not free. If you try the above with a very large array, you'll probably notice a temporary glitch in the UI while the GC is working. That's called a GC pause.

Unlike data structure in Elm, data structures in JavaScript are intended to be mutated, and are not optimized for copying. Although the browser vendors have done a lot of work to optimize this, at the end of the day, you're always better off using appropriate data structures. For instance, while copying an array is not exactly slow, just pushing a value to the end of the array is going to be a whopping 100x faster.

// Copying
const array = [1, 2, 3]
const newArray = [...array, 4]

// Pushing
const array = [1, 2, 3]
array.push(4)

Changing a value somewhere in the array is even faster when using idiomatic JavaScript — thousands of times faster.

// Copying
const array = [1, 2, 3]
const newArray = array.map(function (n, index) {
        if (index == 1) return 'replaced'
        return n
})

// Assigning
const array = [1, 2, 3]
array[1] = 'replaced'

As usual, with performance issues, one has to consider the bigger picture as well. For very small arrays — less than a few thousand items — anything goes, pretty much. You can get really sloppy without feeling any impact in the UI.

The last statement comes with some caveats. If you're processing thousands of items over and over again (e.g., once every frame), the GC is going to be working a lot harder if you copy, and that may actually show up as a UI glitch. This is to say that you generally want to keep all options on the table, just in case you notice something's off.

I myself tend to prefer mutating. Here are my reasons for this:

  • Once you get the muscle memory for how to deal with mutations (not just for individual values but as an overall design of the whole app), it's not such a big deal anyway.
  • I want consistent handling of the values in different parts of the code, wherever it makes sense.
  • Mutation is idiomatic JavaScript. My code will look less out of place.

So where does this idea come from?

The idea that const has something to do with immutability probably comes from a perception that references to values cannot be changed just like in mathematics (in mathematics x = 12 is not a variable declaration, but a declaration of immovable truth). Combined with truly immutable data structures, it allows a way of working that should result in more reliable software.

So it's not that const makes values immutable, but it's one (small) part of a bigger picture. If you're going that route, I think you're using the wrong language. Try Elm, for example. Sometimes taking things to the extreme is the only way to learn something new. Using const won't do anything spectacular, and it certainly won't make anything immutable or your software objectively more robust.

Posted in Programming tips
Back to top