A brief introduction to “Data Immutability” in TypeScript
By: Uday Hiwarale
Tags: Nodejs, JavaScript, Deno, Programming, Typescript

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 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.