Mutable and Immutable Types in JavaScript (With Examples)
Mutability and Immutability are core principles in various programming paradigms.
JavaScript being one of the most used languages on the Internet, mastering what is mutable and immutable types in JavaScript is a plus to acing this common interview question in programming. In this article, we’ll answer this question with examples to make the concept clear.
Mutable and Immutable Types
So what are mutable and immutable types in JavaScript?
Mutable objects are objects whose value can change once created, while immutable objects are those whose value cannot change once created. In JavaScript numbers, strings, null, undefined and Booleans are primitive types which are immutable. Objects, arrays, functions, classes, maps, and sets are mutable.
Mutable types have individual identities and are compared by reference, that is, variables hold references to objects, or rather they point to the memory location of an object. Immutable types don’t have individual identities hence are compared by value, i.e. the value in the variable and not the location they are referencing.
Here is a code snippet to show what we mean by objects being mutable.
var car = { color: 'red', tyres: '4' } var anotherCar = car; //here we assign the value of car to anotherCar. car.color = 'blue'; //we alter a value in the original object console.log(anotherCar.color); //this shows blue because it is referencing the memory location of the car object. console.log(car === anotherCar) //true (because comparison is by reference)
What it means for mutable types is that, when we change the value of a variable, changes apply across all references to that variable.
Here is a code snippet to show what we mean by strings being immutable
var aString = "This is a string"; var bString = aString; //now we alter aString aString = "The string has changed"; console.log(aString); //The string has changed console.log(bString); //This is a string console.log(aString === bString); //false
When you modify a string, a whole new string is created, and the name of the variable assigned to its memory reference. There’s no way of changing the internal state of an immutable type, so the variable simply gets reassigned to a new reference. Same goes for numbers.
Let’s define and assign values to some variables at the beginning of our script, then create some functions to see if we can manipulate the values in our variables.
// The Primitive types (We'll go with numbers and strings) let someNumber = 50; let someString = "This is an immutable string."; //Non primitive types (We'll go with arrays and objects) let someArray = [1,2,3,4,5,6]; let somePersonObject = { name:"Christole", age:40, gender:"female" } // Defining a function that, ideally should change the Primitive value function addSomeNumber(someNumber, valueToAdd) { someNumber += valueToAdd; console.log(someNumber); } //Defining a function that, ideally should change the string function changeString(someString) { someString += " Maybe I'm not an immutable string"; console.log(someString); } //Defining a function that should change the array function addToArray(someArray) { someArray.push(500000); console.log(someArray); } //Defining a function that should change the age of a person in the person object function GrownOlder(somePersonObject) { somePersonObject.age=41; console.log(somePersonObject); }
Let’s walk through the code step by step.
When we run the addSomeNumber(someNumber, 2) function, by passing in someNumber as a parameter, it will perform operations on this variable. Within its function scope, the value of someNumber will be 52, i.e. (50+2). Ideally, we would expect the variable outside the function scope to also change to 52, but that does not happen. So, after you’ve run the addSomeNumber(someNumber, 2) function and then run:
console.log(someNumber) // the value will still be 50.
The same scenario goes for someString variable and the changeString(someString) function. This means we can run this function repeatedly and still get the same result, since the initial values in the string or number do not get altered. What of objects and arrays? Read on to find out.
When we run the addToArray(someArray) function, by passing in the array which initially had 6 items, it will now have 7 items. When you run console.log(someArray) it will now have 1,2,3,4,5,6,500000. This is because an array is mutable; an array’s variable name references a memory location, rather than in a string or number where a variable name points to the value it holds.
When we run the GrownOlder(somePersonObject) function, it updates the person’s age to 41. Since objects variables are by reference, the object is changed globally. Another call to the object from anywhere in your code will have the updated values. This explains what we mean by objects being mutable, the original value can be modified. You can also add values to the object. Let’s say we want to add education level, then we can write somePersonObject.education = “doctorate”;
Another confusion comes when working with the const keyword. Ideally, when a primitive data type (strings, numbers) is declared using the const keyword, then you won’t be able to reassign values to it. Declaring an object with const keyword doesn’t change the behavior of its mutability. It only prevents you from assigning this variable to something else. Consider the following example;
const obj = { number:5 } const objCopy=obj; //do some increments on the object obj.number++;
When you display the obj values, it will have number as 6, even though you had declared it with the const keyword. We had assigned obj to objCopy, if you display the value of objCopy, it will also have number as 6.
It is important to be aware of this mutation when working with objects. When two or more objects are referring to one object, a change in one object’s properties is reflected in all the other objects. This can be explained using this code;
const obj1 = {name:"James"} const obj2 = obj1; obj2.name = "Jack"; //If we display the values of obj1, it will have Jack as name. console.log(obj1.name); // Jack //let's declare another obj and assign obj1 to it const obj3 = obj1; //We create a new field in obj3 obj3.age = 20; //the age field is reflected on all instances of the obj //now if we try to display the values of obj1, it will have the new field as well. console.log(obj1); // {name:"Jack", age:20}
Why not knowing Mutable and Immutable types in JavaScript can land you in trouble.
Consider you have multiple threads working on the same object. You cannot predict the outcome of your code since the action of one thread will modify the object, the next thread using this object as a parameter will get totally different data and there’s no guarantee that the operation will be safe.
You cannot keep a copy of an object by assigning it to a variable first, then modifying the properties of the original object. The property values in the copy will also change.
With immutable types, we know that a value’s state won’t be changed once we create it hence not resulting in unexpected logic in our code. For instance, we want to compare two email strings to authenticate a user. If a string was mutable and a part of our code had altered the email string inside some other method, then the user wouldn’t be able to log in.
Can we create immutable JavaScript objects?
Fortunately, yes. JavaScript being a ubiquitous language that supports various programming paradigms, there is, of course, a fix to most issues. JavaScript has the Object.freeze() function. Calling the function after creating your objects marks all the properties of the object immutable.
If you try to reassign a property value, it will silently fail or in case you have strict mode turned on, an exception will be thrown. Here is a code sample;
obj4 = {name:"James"} obj5 = obj4; Object.freeze(obj4); console.log(obj5); // {name: "James"} obj4.name = "Daniels"; //Trying to modify the property value. Note that this won't throw an error, however, it won't change the value of the name property. console.log(obj4); // {name: "James"} console.log(obj5); // {name: "James"}
Facebook released a library called Immutable.js to work with React.js, a front-end framework, also by Facebook. This library is geared towards easy persistence of objects, copy and comparisons, simpler reasoning, and less complicated coding to avoid errors of mutable types in JavaScript.
React.js renders components to the UI. If there’s a change in a component’s properties, React.js uses a shallow comparison to know if there are changes before re-rendering. Hence the need to have a library that ensures immutability in JavaScript.