A brief introduction to Data Immutability in TypeScript

In this lesson, we are going to learn a few techniques to make values immutable, both at compile-time and at runtime.

JavaScript is not so strong when it comes to data immutability. Internally, all primitive data types like string, number, boolean etc. are immutable which means you can’t mutate the value once it is set to a variable. You can only assign a new value to a variable and the old one will be garbage collected by the JavaScript engine when the time comes.

That’s not the same story with objects. If you create a plain object using an object literal, you can override the value of a property or add or remove a property to or from an object whenever you want.

var obj = { a: 1 }; // define an object
obj.b = 2; // add new property
obj.c = "hello"; // add new property
obj.b = 3; // update property value
delete obj.b; // delete property

Objects are by default mutable as shown above and they are passed by reference (but not in a way that operates in other languages). When you assign an object to a variable, the object value doesn’t get copied. JavaScript only assigns a reference of the object to the new variable. The same principle applies when you pass an object as an argument to a function or returns an object from a function. However, there are preventive mechanisms you can apply to make objects immutable.

JavaScript provides Object.defineProperty function to add a new property on an object with customized property settings. You can also use this function to modify settings of an existing property on an object. This setting of an individual property is called a property descriptor.

💡 You can follow the MDN documentation to know more about the property descriptor or read the first part of my article on JavaScript decorators.

In the above example, we have created a ross object using object literal expression. Since by default, object properties are writable, we can assign a new value to firstName property and we can see it changing.

The writable option of the property descriptor if set to false makes the property read-only. Hence even though we have assigned string value Jack to the ross.firstName, the value stays the same. However, in strict mode, it will throw an error since we are trying to modify the value of a read-only property.

If you want to add new properties or modify existing properties with custom property descriptors at once, then you can use Object.defineProperties function. If you want to create a fresh object with custom property descriptors, then you can go for Object.create function.

Whichever the case, handling data immutability is tough in JavaScript. But there are some quick and easy methods that you can implement to make your life less miserable.

JavaScript provides Object.[freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) method which basically freezes an object like ice. You won’t be able to add or remove properties to or from the object. You also won’t be able to override the values of the properties or configure their property descriptors. Let’s see a simple example.

In the above example, we have created a simple ross object. This object is mutable in every which way, so we froze it using Object.freeze method. Now the object is basically dead. As you can see, we tried to override a property value and add a new property, but nothing happened. In strict mode, this operation will throw an error at runtime.

💡 The reason we used ross as any type assertion because TypeScript won’t let you access or assign a property that doesn’t exist on the object ross object which contains only the firstName and lastName properties.

JavaScript also provides Object.seal method which is less aggressive compared to freeze method. This method prevents a new property being added to the object or configuring existing properties. It won’t prevent you from assigning new values to properties as long as they are writable.

💡 The most important thing to be careful about Object.freeze or Object.seal methods are that these methods only work on the properties of an object. If the value of a property is another object, its properties won’t be affected. So techically, your object is not immutable as one can modify the nested property value.

Immutability using the const keyword

ES6 brought a lot of good features to the language one of which is the let and const keywords. We were used to declaring variables using var but there was no mechanism to create constants in JavaScript. But in modern JavaScript, const declares a constant.

When a variable is declared using const, its value is fixed as in you won’t be able to mutate it, ever. The const keyword, however, is the most misunderstood one. A constant doesn’t make the value immutable, it just prevents the assignment of the new value to the variable name.

In the above program, we created a constant ross which is a plain JavaScript object. We can still modify the firstName and lastName property values since they are not read-only. However, we won’t be able to change the value of ross constant using a value assignment syntax.

Immutability using the readonly keyword

The const keyword is a JavaScript keyword which means you can make a variable immutable natively. However, TypeScript provides a readonly keyword that can be used as a compile-time check to avoid mutation of object properties, class properties, array, etc.

Read-only Interface fields

TypeScript provides readonly keyword that can be used to annotate a field in an interface as read-only. Any attempt to override the property value marked readonly will result in a compile-time error.

In the above program, the firstName field of the Person interface is read-only. Since ross is a type of Person, TypeScript won’t let us assign a new value to the firstName field.

💡 TypeScript doesn’t modify the property descriptor of the firstName property in the compiled JavaScript code. The readonly keyword is just a compile-time check but it should suffice the need in most of the cases.

Read-only Class fields

Like an interface, we can mark fields of a class read-only. We need to use the same readonly keyword before the field name declarations.

In the above example, we have marked firstName field of the Person class read-only. We are allowed to set the initial value of the firstName field from within the constructor function. But once the instance object is created, we won’t be able to mutate its value.

💡 You can also use public, private or protected access modifiers with readonly keyword provided that access modifier keyword should come before the readonly keyword. You can also use parameter property declaration using just readonly keyword or alongside an access modifier.

Array Immutability

There is no efficient way to freeze arrays in JavaScript at the moment. Hence all you can do is hope that someone in your team hasn’t written a code that mutates an array. But TypeScript does have a mechanism to prevent this.

You can use readonly keyword in the type annotation of an array. This instructs TypeScript to forbids any value addition to the array or changing the length of an array. Though this doesn’t prevent assigning a new array value to the array variable, hence there is no harm throwing const keyword in there.

As you can see from the above program, any attempt to modifying the internal structure of an array resulted in failure. Similar to an object, arrays are also passed by reference since an array is basically an object. Hence, the TypeScript compiler won’t let you assign a read-only array to a mutable array.

💡 You can also use ReadonlyArray global utility type provided by the TypeScript to create read-only arrays but we will look into this in the next tutorial. Similar to array, you can also create read-only tuple by putting readonly keyword before a tuple type such as readonly [number, string].

#typescript #data-immutability