The Case for Immutability

Immutability is the notion that something cannot be changed. It has become a buzzword lately because of its pervasiveness throughout modern languages like ClosureScript and Elm. Why would you want data that can never change, when data is constantly changing in modern applications? One of the harder problems to solve for is how to detect when data has been updated. When we know that, we can update all of the pieces that need to respond.

Let’s look at a common case in JavaScript where we want to check if a value has changed.

There’s no trick to this example. We’re taking a variable named data, and creating a newData variable that is equal to the original. When we check if the two variables are equal, as expected it’s true. When we change the value of newData and check the equality again, as expected, we see that they are unequal, thus the newData has changed.

This works because when we declare the data variable, it is immediately given a value. Then we do the same with newData and set its value to the value of data. When we set the value of newData to something else, as expected, it is not equal to the value stored in the data variable.

So far, there’s no problem, right? With primitives in JavaScript, when we declare a variable, that variable effectively is a placeholder for its value. When we change the value, the variable changes. Taking that a step further, change its value changes the variable itself.

This isn’t true for non-primitives in JavaScript. Let’s see how this breaks down with objects.

Even though we changed the value of data2, JavaScript still evaluated the objects to be true. In fact, if you were to inspect data, you’d find that its value had changed as well.

When a variable is set to a non-primitive in JavaScript, we aren’t creating a variable and setting its value, we’re actually creating a reference to the original object. Complex structures in JavaScript are allocated in memory, and variables simply become references to those memory allocations. Because of this, using our approach, we will never be able to check if data has changed.

So how do we solve for this? It’s actually easy, we just make a copy of the original object when we create our second variable, using the Object.assign method.

Object.assign copies subsequent objects’ properties into the original object that is passed to it. What we’re doing in the code above is creating a brand new object, then copying the properties from data into it, and then copying any new properties, giving a brand new object. Another positive from embracing immutable data is that our data becomes way more understandable. We now know that data is different from data2, and how it is different. Alternatively, if you just wanted to create an exact copy of an object, you could also use the Object.create method.

Likewise, when dealing with arrays, you’ll find the following.

You might worry that it’s inefficient to create copies of objects every time you want to change a property, but in reality, this is only an issue with particularly large data structures. When using this approach, it is best to keep your data structures relatively small. The JavaScript engine will destroy objects from memory when there are no longer references made to them, so by replacing and old object with a new copy, you are creating an immutable object reference. We can now compare when changes happen, making it easier to write incredibly performant code.

Using React as an example, you can tell your component to only update when the data it is watching changes. Using immutability, this becomes very simple, and it is now easy to write highly optimized components.

There are more ways to deal with immutability, however. Next let’s look at using modern JavaScript, or ES6, with its spread operator. The spread operator, or a preceding , tells JavaScript to merge in the properties of another similarly-typed non-primitive object.

This works just the same as before, the syntax is way more natural than using Object.assign or [].concat, but it becomes obtuse when working with more complex data.

We find that we are constantly spreading in different layers of data. For me, this feels a little too verbose, especially when dealing with something as simple as updating one property on a nested record. We also have the problem that whenever we attempt to update a record, even if the data doesn’t change, we are returned a new object. We are effectively now able to test if an attempt to change data was made, but not if the data was actually changed.

Luckily, there’s a library named Immutable.js, written by Facebook, that provides a very robust suite of Immutability helpers. Not only is the syntax much cleaner using the Immutability library, but manages your state as a set of smaller objects, internally. Here’s how we’d solve the same problem with Immutable.js:

This approach, to me, is vastly more readable. The immutable object has several methods, set, setIn, update, updateIn, merge, deepMerge, that will return a new object as well as update any parameters that you pass to it. There are also many functions that help you iterate over this complex object types just like you would iterate over natural JavaScript objects and arrays. Immutable.js also supports a great deal more than the natively provided primitives, including Map, OrderedMap, List, Record, Seq, Collection, and more. It is likely to be overkill in a lot of projects as it is extraordinarily comprehensive, however when you need to update and compare large, complex data sets, it has invaluable.

Let’s look at a real world example. Let’s say we’re building a friend list, and we only want it to re-render when the data it cares about is changed. Let’s say the friends list relies on a total user count, whereas individual user items need to update when any map of properties changes.

We can see the list component will only update when the user information changes and likewise, the list item component will only change when the information that it’s concerned with is updated. When using Immutable.js, we don’t have to worry about new memory allocations being created for objects unless the data actually changes, which means that we can be sure that the user interface only updates when data actually changes.