JavaScript in Plain English

New JavaScript and Web Development content every day. Follow to join our 3.5M+ monthly readers.

Follow publication

Mastering Immutability in JavaScript

Yogesh Datir
JavaScript in Plain English
8 min readDec 18, 2024

Photo by Nick Morrison on Unsplash

The Perils of Mutability: A Simple Example

Consider a simple scenario where we have a person object:

const person = {
name: 'Alice',
age: 30
};

Now, let’s say we want to create a new person object with an updated age:

const newPerson = person;
newPerson.age = 31;

While it might seem like we’ve created a new person, in reality, we’ve just modified the original person object. This can lead to unexpected behavior in larger applications, especially when multiple parts of the code rely on the same mutable object.

Enter Object Immutability

Object immutability is a crucial concept in JavaScript that refers to the inability to modify an object after it’s created. This concept is important for writing predictable and maintainable code, especially in functional programming paradigms.

Key Concepts of Immutability

  • Definition: An immutable object is one whose state cannot be modified after it is created.
  • Benefits: Immutability leads to more predictable code, easier debugging, and better performance in certain scenarios.
  • Challenge: JavaScript objects are mutable by default, so achieving immutability requires specific techniques.

The principle of object immutability

The principle of object immutability in programming refers to the concept that once an object is created, its state cannot be modified. This means that the object’s properties and values are read-only after its initial creation. Immutability is a key concept in functional programming and is used to promote purity, predictability, and simplicity in code. Here’s a breakdown of the core aspects of object immutability:

Core Aspects of Object Immutability

  1. Predictability: Immutable objects lead to more predictable code since their state cannot change unexpectedly. This predictability facilitates easier debugging and understanding of the code, as the flow of data is more straightforward.
  2. Thread Safety: In concurrent programming, immutable objects are inherently thread-safe since they cannot be modified after creation. This eliminates the need for synchronization when accessing objects across different threads, reducing the risk of concurrency-related issues like race conditions.
  3. Simpler State Management: Immutability simplifies state management, especially in complex applications. Since state changes are explicit and involve creating new objects rather than mutating existing ones, it becomes easier to track changes over time and manage application state.
  4. Facilitates Functional Programming: Immutability is a cornerstone of functional programming. It encourages the use of pure functions — functions where the return value is only determined by its input values, without observable side effects. This leads to more reliable and maintainable code.

Achieving Immutability in JavaScript

While JavaScript doesn’t enforce immutability by default, you can achieve it through careful programming practices and the use of appropriate techniques:

1. Primitive Data Types:

  • Primitive data types (numbers, strings, booleans, null, undefined, and symbols) are inherently immutable. Assigning a new value to a variable simply creates a new reference to a different value.

2. Object Immutability:

  • Creating New Objects: To modify an object, create a new object with the desired changes.
  • Spread Operator: Use the spread operator to create a shallow copy of an object and then modify the copy.
  • Object.assign(): This method can be used to create a new object by copying properties from one or more source objects.
  • Libraries like Immer: Immer is a popular library that simplifies the process of creating immutable objects. It allows you to work with draft objects as if they were mutable, and then generates a new immutable object with the changes.

Convenient Ways to Implement Object Immutability in JavaScript

The most convenient way to implement object immutability in JavaScript, especially for complex objects, is to use a combination of programming practices and libraries designed for immutability. Here’s a concise overview:

Using ES6 Features

  • const Keyword:

Using const prevents reassignment of the variable, but doesn't make the object itself immutable.

const obj = { prop: 42 };
obj.prop = 33; // This works
obj = {}; // This throws an error
  • Spread Operator for Arrays and Objects: Use the spread operator (...) to create copies of arrays and objects with modified values to avoid modifying the original.
const originalArray = [1, 2, 3];
const newArray = [...originalArray, 4]; // Immutable push

const originalObject = { a: 1, b: 2 };
const newObject = { ...originalObject, c: 3 }; // Immutable add property

Object.assign()

Similar to the spread operator, Object.assign() can create a new object:

const originalObj = { prop: 42 };
const newObj = Object.assign({}, originalObj, { newProp: 33 });

Libraries for Deep Immutability

  • Immer: Allows you to work with immutable state in a more natural way, using a draft state and producing the next immutable state without mutating the original state.

Example Using Immer:

import produce from "immer";

const originalState = {
data: {
user: {
profile: {
address: {
city: 'New York'
}
}
}
}
};

// without immer
const newState = {
...originalState,
data: {
...originalState.data,
user: {
...originalState.data.user,
profile: {
...originalState.data.user.profile,
address: {
...originalState.data.user.profile.address,
city: 'Los Angeles'
}
}
}
}
};

// with immer
const newState = produce(originalState, draft => {
draft.data.user.profile.address.city = 'Los Angeles';
});
  • Immutable.js: Provides persistent immutable data structures that are easy to use.
import { Map, fromJS } from 'immutable';

const originalState = {
data: {
user: {
profile: {
address: {
city: 'New York'
}
}
}
}
};

// without immutable
const newState = {
...originalState,
data: {
...originalState.data,
user: {
...originalState.data.user,
profile: {
...originalState.data.user.profile,
address: {
...originalState.data.user.profile.address,
city: 'Los Angeles'
}
}
}
}
};

// with immutable
const originalState = fromJS(originalState);

const newState = originalState.updateIn(['data', 'user', 'profile', 'address', 'city'], () => 'Los Angeles');

Benefits of Immutability

Immutability is a programming concept where objects cannot be changed after they are created. Instead of modifying existing objects, you create new objects with the desired changes. This approach has several benefits, including:

  • Predictable State: Immutable objects make it easier to reason about the state of your application, as you can be certain that a particular object won’t change unexpectedly.
  • Easier Debugging: By tracking changes to immutable objects, you can more easily identify the source of bugs.
  • Improved Performance: In some cases, immutable data structures can be optimized for performance, especially in functional programming contexts.
  • Enhanced Concurrency: Immutable objects are inherently thread-safe, making them suitable for concurrent programming.

Immutability in Practice

1. Redux and State Management

Immutability is a core principle in Redux and other state management libraries, ensuring predictable state updates.

2. React and Performance

React relies on immutability for efficient change detection and rendering optimizations.

3. Functional Programming

Immutable data structures are fundamental to functional programming paradigms in JavaScript.

Challenges and Considerations

  • Deep Immutability: Achieving deep immutability for nested objects requires recursively creating new objects for each nested level that can be tedious and error-prone for complex structures, or use of libraries.
  • Performance: Creating new objects instead of mutating can impact performance in high-frequency operations.
  • Compatibility: Some older JavaScript environments might not support modern immutability techniques.

Updating and Removing Properties in Immutable Objects in JavaScript

Updating or removing properties from an immutable object in JavaScript can be achieved through various methods. Here’s a list sorted by convenience and robustness:

1. Using the Spread Operator for Updating

Most Convenient for Simple Updates

To update a specific property of an immutable object in JavaScript, you can use the spread operator (...) to create a new object with the updated property value. This approach maintains the immutability of the original object by not modifying it directly but instead creating a new object with the desired changes.

Example

const originalObject = { a: 1, b: 2, c: 3 };

// Update property 'b'
const updatedObject = { ...originalObject, b: 20 };
console.log(originalObject); // { a: 1, b: 2, c: 3 }
console.log(updatedObject); // { a: 1, b: 20, c: 3 }

This method is convenient for shallow updates. For nested objects, you would need to apply the spread operator at each level of nesting where updates are required, which can become verbose. For deep updates, consider using libraries like Immutable.js or Immer for more concise and readable code.

2. Using Destructuring and Rest Operator for Removal

Convenient for Single or Few Properties

const { b, ...newObjectWithoutB } = originalObject; // Removes 'b'

3. Using a Utility Function for Multiple Updates or Removals

Full Proof for Complex Operations

  • Update Function
function updateObject(obj, updates) {
return { ...obj, ...updates };
}

const updatedObject = updateObject(originalObject, { b: 20, c: 30 });
  • Remove Function
function removeObjectProperties(obj, ...propsToRemove) {
let result = { ...obj };
propsToRemove.forEach(prop => delete result[prop]);
return result;
}

const newObject = removeObjectProperties(originalObject, 'b', 'c');

4. Using Libraries like Immer or Immutable.js

Most Robust for Deeply Nested Structures

  • Immer for Immutable Updates
import produce from 'immer';

const nextState = produce(originalObject, draftState => {
draftState.b = 20; // Update
delete draftState.c; // Remove
});
  • Immutable.js for Immutable Data Structures
import { Map } from 'immutable';

let map1 = Map({ a: 1, b: 2, c: 3 });
map1 = map1.set('b', 20).delete('c');

The choice of method depends on the complexity of the operation and the structure of the object. For simple updates or removals, native JavaScript features like the spread operator and destructuring are most convenient. For more complex or deeply nested updates, utility functions or libraries like Immer and Immutable.js provide robust solutions.

Best Practices for Immutability:

  • Avoid Mutating Objects Directly: Always create new objects to represent changes.
  • Use Functional Programming Techniques: Functional programming emphasizes immutability and pure functions, which can help you write more predictable and maintainable code.
  • Leverage Libraries: Libraries like Immer can simplify the process of working with immutable data structures.
  • Consider Performance Implications: While immutability can bring many benefits, it’s important to be mindful of performance implications, especially for large data structures.

By following these guidelines, you can effectively leverage immutability in your JavaScript applications to write cleaner, more reliable, and more performant code.

Conclusion

Object immutability is a fundamental concept in modern JavaScript development that empowers developers to write cleaner, more predictable, and maintainable code. By preventing direct modification of objects, immutability enhances code reliability, simplifies state management, and facilitates functional programming paradigms.

While JavaScript doesn’t inherently enforce immutability, a combination of language features (like const, spread operator) and libraries (such as Immutable.js or Immer) provide powerful tools to achieve this goal. For most applications, a judicious blend of these techniques is sufficient. However, for complex state management scenarios, specialized libraries can offer significant performance and efficiency benefits.

By embracing immutability, developers can significantly improve the overall quality of their JavaScript applications, leading to more robust, scalable, and future-proof software.

In Plain English 🚀

Thank you for being a part of the In Plain English community! Before you go:

Published in JavaScript in Plain English

New JavaScript and Web Development content every day. Follow to join our 3.5M+ monthly readers.

Written by Yogesh Datir

A developer passionate about creating efficient, user-friendly apps with JavaScript and React. I love learning new tech and sharing what I know.

Responses (1)

Write a response

Well it seems Records and Tuples are going to come to simplify immutable Data structures

--