Javascript Immutability and Optimization

Background

Immutable, a buzzword that I hear a lot but never quite understood the importance of it until recently. Personally for me, its difficult for me to see the importance of anything until I understand the practical applications for it. Immutability was a topic discussed towards the end of the last ReactNYC Meetup I attended which puts this term into perspective.

Nir Kaufman’s presentation on immutability primarily focused on optimization. Although I wasn’t able to fully understand each example while there, I did my own research afterwards. Slowly, but surely, I kept going down the rabbit hole on this concept, attempting to under the nuances of each example Nir provided, such as structural sharing, copy-on-write, and Immer.js. Although I am not an expert on this topic by any means, I did learn a lot of new optimization techniques that may help any future coder.

Note: This topic on immutability will focus primarily on Javascript objects.

Immutable

Before we dive into the techniques, let’s first discuss what it means when an object is immutable. According to Wikipedia’s definition:

In object-oriented and functional programming, an immutable object (unchangeable[1] object) is an object whose state cannot be modified after it is created.

We can go even broader and look at Merriam-Webster’s definition of immutable:

: not capable of or susceptible to change

For the sake of analogy, one can think of a const variable assigned to a primitive data type as being immutable. By the definition of a const variable, it cannot be re-assigned a new value after initialization. Because a value cannot be re-assigned, the const variable can be seen as immutable.

Note: All examples and techniques shown below correlates more towards Merriam-Webster’s definition of immutable than Wikipedia’s definition as I am primarily focusing on the “cannot be modified” aspect.

From my research, and Wikipedia’s definition, it seems that immutability normally refers to objects.

By default, Javascript objects are mutable. This means that assigning a new key-value pair or changing an existing key-value pair is possible. As a quick example:

Quick overview of a Javascript object

Line 1 instantiated the object, line 3 changed an existing key-value pair, and line 5 assigned a new key-value pair.

However, what if I wanted to make the object immutable after changing and adding those key-value pairs? You can with Object.freeze() ! According to its documentation:

The Object.freeze() method freezes an object. A frozen object can no longer be changed; freezing an object prevents new properties from being added to it, existing properties from being removed, prevents changing the enumerability, configurability, or writability of existing properties, and prevents the values of existing properties from being changed. In addition, freezing an object also prevents its prototype from being changed. freeze() returns the same object that was passed in.

In other words, no one can change the object in any way, shape, or form… for the most part. Let’s test this function out from our previous example.

Implementing Object.freeze()

On line 8, a new variable called immutableObject was instantiated with the frozen mutableObject variable. Lines 9 and 10 changed and added key-value pairs to immutableObject but line 12 shows that nothing changed within the object.

Huzzah! Looks like we made out object immutable at this stage. Another fun fact with Object.freeze() is if you store the frozen object in a new variable, it actually points to the same place in memory as the object you wanted to freeze. Line 13 shows that.

Getting back to the for the most part, Object.freeze() does not work for nested objects. This implies that Object.freeze() does a shallow freeze on the object that you pass through. The gist below demonstrates this:

As you can see from the gist above, in line 5, I changed the value in the nested object and it showed in line 6. If you want to freeze a nested object, you would need to freeze each layer of the object whose value is an object type.

Immer.js

Since Javascript does not innately have immutability, libraries were developed to accommodate this. Two popular libraries, Immer.js and Immutable.js, seem to be the popular choices. In this case, I would be diving into Immer.js.

The beauty, and also confusing part, of Immer.js is that it uses structural sharing. Structural sharing is analogous to persistent data structure, in which Wikipedia defines it as:

In computing, a persistent data structure is a data structure that always preserves the previous version of itself when it is modified. Such data structures are effectively immutable, as their operations do not (visibly) update the structure in-place, but instead always yield a new updated structure.

In light of learning another new topic, I had to scan far and wide into the abyss of Google to further my knowledge. I came across two blogs that goes into detail on structural sharing and Immer.js/

Essentially, structural sharing keeps the original structure as before and the only portion of it that changes is creating a new portion in the original structure that points to the new value. By mostly keeping the original structure, it is effectively making the object/array immutable.

Let’s take a quick step back and look at reducers in your Redux store. Conventionally, if there were any changes to the state in your reducer, you would use the spread operator to make a copy of the original state, apply any necessary changes, and then update the state with the new changes. By using the spread operator, you are copying and creating new data, line by line, within your object/array although the data is exactly the same as before. If this is scaled for 100,000 items, you would be duplicating 100,000 items (with the exception of where it needs to be updated), and therefore eating a ton of memory and slowing down your application.

With Immer.js, and its produce() function, structural sharing avoids this from happening. For argument’s sake, in an object, in order to access a value based on its key, the computer follows a “route”. Each possible “route” makes up the original structure of the object.

Note: The term “route” is not an actual term used in the this topic but I created it for ease of explanation.

Now, lets say that you need to update an item within your state. With Immer.js, it can determine the specific “route” where the item needs to updated and keep the “routes” of all the non-updated items through structural sharing. This means that a new “route” is created to reflect the new value whereas all the old “routes” are the re-used/the same because there are no changes to its current value. If you were to use the spread operator in this analogy, you would be re-creating all the same “routes” although they all point to the same previous value.

If you would like to read about the data structure and understand the technical, proper explanation, please read about tries.

Key Takeaway

Although I didn’t get into copy-on-write, please feel free to explore the topic yourself. The key takeaway from all of this research is that code can always be optimized in some way. Although this blog was referring to optimizing with respect to speed, you can also optimize with respect to memory, like refactoring your code. There are small ways to optimize as well such as memoization (e.g., React.memo() ). Every little bit of optimization will go a long way.

--

--

--

Full stack software developer with experience in Javascript, React, Redux, and Ruby on Rails and a background in Mechanical Engineering.

Love podcasts or audiobooks? Learn on the go with our new app.

Make your own online compiler in React ⚛️

Providing Cross Domain access to images and data in Codepen

Understanding Zowe CLI Distributions

The Zowe CLI Distribution, illustrated.

How to return the response from an asynchronous call?

Deploying a React and Node app to Heroku

Ionic 5 Grocery Shopping— User, Delivery person, and Admin Panel: Features Overview

Ionic 5 Grocery Shopping- User, Delivery, and Admin Apps: Features Overview

Shadow DOM: fast and encapsulated styles

Angular Common Module. You have seen it but you don’t know how cool it is.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Samuel Guo

Samuel Guo

Full stack software developer with experience in Javascript, React, Redux, and Ruby on Rails and a background in Mechanical Engineering.

More from Medium

Javascript Promise.all vs Promise.race

JavaScript Eğitimi - 1 / Değişkenler, Operatörler ve Kontrol Yapıları

#002 JavaScript with Dimos: Data Types

Expressing Functions in JavaScript