Marcell Ciszek Druzynski

Immutability

Immutability is a powerful concept that promises code reliability, simplifies debugging and make the code more trustable since you know that nothing can't change.

June 07, 2023

The Power of Immutability in JavaScript: Building Robust and Reliable Code

Introduction

In Javascript immutability has been emerged as a powerful concepts that promises code reliability, simplifies debugging and make the code more trustable since you know that nothing can't change. Immutable data structures and practices ensure that once a value is assigned, it remains unchanged throughout the execution of the program. In this blog post, we will explore the significance of immutability in JavaScript, the benefits and how we can implement robust and predictable code using immutable data structures.

What is Immutability?

When something is immutable a property of an object or a value can't be changed after it has been created. In JavaScript, primitive values such as strings, numbers, and booleans are immutable by default. However, objects and arrays are mutable and can be modified, leading to potential issues such as unexpected side effects and bugs. Embracing immutability involves avoiding direct modifications to data and instead creating new copies with desired changes, which make sure that we keep the data consistent throughout the whole program.

The const topic

Many developers often get confused about how the const keyword works in JavaScript. It is commonly mistaken for creating an immutable value, but its actual behaviour is preventing the reassignment of a variable to a different value. However, it does not stop us from mutating the values themselves, especially when dealing with non-primitive types like objects. It's important to note that strings, numbers, booleans, null, and undefined are immutable in JavaScript.

Let's consider this example:

1const numbers = [1, 2, 3, 4];
2numbers = [100, 20]; // Cannot assign to 'numbers' because it is a constant
1const numbers = [1, 2, 3, 4];
2numbers = [100, 20]; // Cannot assign to 'numbers' because it is a constant

In this case, we cannot assign a new list to the numbers variable since it was declared using the const keyword. However, it's worth noting that we can still mutate the existing list:

1const numbers = [1, 2, 3, 4];
2
3// mutating the list
4numbers.push(5);
5numbers.push(6);
6
7console.log(numbers); // [1, 2, 3, 4, 5, 6]
1const numbers = [1, 2, 3, 4];
2
3// mutating the list
4numbers.push(5);
5numbers.push(6);
6
7console.log(numbers); // [1, 2, 3, 4, 5, 6]

Here, we have successfully added two new numbers to the list using the push() method, which mutates the original list. The key takeaway is that the const keyword does not make values immutable; it simply prevents reassignment of the variable to a different value.

Benefits of Immutability:

  • Predictability and Reliability: Immutability guarantees that once a value is assigned, it remains constant, making it easier to reason about the code. Since immutable data cannot be modified accidentally, it eliminates unexpected side effects and ensures predictable behaviour, making your code base less complex and error-prone.

  • Simplified State Management: Immutable data is beneficial in scenarios where maintaining a predictable application state is crucial, such as in Redux or other known state management libraries. By enforcing immutability, state changes become explicit and traceable, leading to more reliable and maintainable code.

  • Performance Optimisation: Immutability enables optimisations like memoization. Memoization, a known technique that caches the results of expensive function calls, if you are familiar with React you probably used the built in hook, useMemo that caches values to prevent unnecessary re-renders.

  • Concurrent Programming: In multi-threaded environments, immutability eliminates the need for locks and synchronisation mechanisms since immutable data can be safely shared across threads without the risk of race conditions. This makes concurrent programming more straightforward.

Implementing Immutability in JavaScript:

  • Using Object.assign() or the Spread Operator: To create new copies of objects or lists with modified values, you can utilise Object.assign() or the spread operator. These methods allow you to clone objects while introducing necessary changes, ensuring immutability.
1const snickers = {
2 name: "Snickers",
3 price: 2.5,
4 description: "Chocolaty, nutty goodness",
5};
6
7const snickersCopy = {...snickers};
8const snickersCopyWithObjectAssign = Object.assign({}, snickers);
9
10console.log(snickers, snickersCopy, snickersCopyWithObjectAssign);
11snickers.price = 100;
12console.log(snickers, snickersCopy, snickersCopyWithObjectAssign);
1const snickers = {
2 name: "Snickers",
3 price: 2.5,
4 description: "Chocolaty, nutty goodness",
5};
6
7const snickersCopy = {...snickers};
8const snickersCopyWithObjectAssign = Object.assign({}, snickers);
9
10console.log(snickers, snickersCopy, snickersCopyWithObjectAssign);
11snickers.price = 100;
12console.log(snickers, snickersCopy, snickersCopyWithObjectAssign);

Here we create two copies of the snickers object using both the spread operator and Object.assign(). When snickers is mutated, it does not affect our copied objects. However, it's important to note that both the spread operator and Object.assign() only perform a shallow copy, not a deep clone. This means that if snickers had a nested object, that nested object would be mutated along with the copied objects.

  • Functional Programming Techniques: Functional programming concepts naturally align with immutability. Utilising higher-order functions like map(), filter(), and reduce() instead of direct mutations can help ensure immutability in your code.
1const names = ["Glen", "Tobias", "Greg"];
2const namesWithPrefix = names.map((name) => `Mr. ${name}`);
3["Mr. Glen", "Mr. Tobias", "Mr. Greg"];
1const names = ["Glen", "Tobias", "Greg"];
2const namesWithPrefix = names.map((name) => `Mr. ${name}`);
3["Mr. Glen", "Mr. Tobias", "Mr. Greg"];

Here we creating a new list instead of modifying the original list by using the Array.prototype.map() function.

  • Freeze objects: is a built-in method that allows you to freeze an object, preventing any modifications to its properties or prototype. Once an object is frozen, you cannot add, remove, or update its properties, and you cannot change its prototype.
1const snickers = Object.freeze({
2 name: "Snickers",
3 age: 2,
4});
5
6// Trying to modify the properties will have no effect
7snickers.age = 3;
8console.log(snickers); // { name: 'Snickers', age: 2 }
1const snickers = Object.freeze({
2 name: "Snickers",
3 age: 2,
4});
5
6// Trying to modify the properties will have no effect
7snickers.age = 3;
8console.log(snickers); // { name: 'Snickers', age: 2 }

Conclusion:

Immutability is a great tool and a powerful concept that promotes solid, reliability, and predictability code in JavaScript code. Where it refers to the property of an object or data structure that prevents it from being modified once it is created. When we use immutable data-structures its state remains constant throughout its lifetime and any operation on the object results in a new object being created instead of modifying the existing one.

Things to remember about immutability in Javascript.

  • Immutable Data: In JavaScript, primitive data types such as strings, numbers, and booleans are immutable by default. Once assigned a value, they cannot be changed. For example, if we have a variable const name = "Marcell";, we cannot modify the value of the variable name to be something else.

  • Immutable Objects: Unlike primitive data types, objects in JavaScript are mutable by default. However, we can achieve immutability by adding certain techniques. One common approach is to use libraries like Immutable.js or Immer, which provide data structures and utilities to create and work with immutable objects.

  • Benefits of Immutability: Immutability brings several advantages. It simplifies state management by eliminating the need to track changes. Immutable data structures are thread-safe, making it easier to reason about concurrent or parallel code. Additionally, immutability enhances performance as it allows for efficient change detection and enables caching and memoization techniques.

  • Immutability and Functional Programming: Immutable data plays a important role in functional programming paradigms, where functions avoid side effects and operate on immutable inputs to produce predictable outputs. Immutability enables functions to be pure, meaning they produce the same result for the same inputs and do not modify any external state.

  • Immutability Techniques: JavaScript provides a few techniques to achieve immutability without external libraries. Methods like Object.freeze() to prevent modification of objects, and employing array methods like filter(), slice(), and map() to create new arrays based on existing ones. Where we create a new array instead of modifying the existing one.

In summary, immutability in JavaScript ensures that data remains unchangeable after its creation, promoting stability, predictability, and simpler state management. By employing immutability techniques, we can enhance our code's performance, facilitate functional programming principles, and create robust and maintainable applications.