Mutable and Immutable in TypeScript

Mutable and Immutable in TypeScript

This article explains mutable and immutable concepts in TypeScript.

YouTube Video

Mutable and Immutable in TypeScript

What is Mutable?

Mutable means that a value can be changed. Reference types such as objects and arrays are typical examples of mutable data structures.

Example of a Mutable Object

1type Person = { name: string; age: number };
2
3// Mutable Example: Object
4let person: Person = { name: "Alice", age: 25 };
5person.age = 26;
6console.log(person); // { name: "Alice", age: 26 }

In this code, the age property of the person object is changed from 25 to 26. Since objects are passed by reference, the contents at the memory address stored in the person variable are modified.

Example of a Mutable Array

1// Mutable Example: Array
2let numbers: number[] = [1, 2, 3];
3numbers.push(4);
4console.log(numbers); // [1, 2, 3, 4]

In this code, the push method is used to add a new element 4 to the original array. This modifies the original array, making it a mutable operation.

Example in a Function

1// Mutable Example: Function
2function append(arr: number[], value: number): void {
3    arr.push(value); // Modify the original array
4    console.log(arr);
5}
6
7let nums: number[] = [1, 2, 3];
8append(nums, 4);
9console.log(nums); // [1, 2, 3, 4]

When performing mutable operations inside a function, the original array is also modified.

What is Immutable?

Immutable means that a value cannot be changed. Primitive types are fundamentally immutable.

Example of an Immutable Primitive Type

1// Immutable Example: String
2let str: string = "hello";
3str[0] = "H"; // Error: Index assignment is not allowed
4console.log(str); // "hello"

Attempting to change the first character of the string str to H fails because strings are immutable.

Example in a Function

1// Immutable Example: Function
2function increment(num: number): number {
3    num++; // This modifies only the local copy of num
4    return num;
5}
6
7let number: number = 10;
8console.log(increment(number)); // 11
9console.log(number); // 10 (original number remains unchanged)

Since numbers are immutable, operations inside a function do not affect the original variable.

Immutable Operations on Arrays

Arrays are mutable, but by creating a new array instead of modifying the original one, immutable operations can be achieved.

1// Create an array of numbers
2let numbers: number[] = [1, 2, 3];
3
4// Immutable Example: Creating a new array
5let newNumbers: number[] = [...numbers, 4];
6
7console.log(numbers); // [1, 2, 3] (original array is unchanged)
8console.log(newNumbers); // [1, 2, 3, 4] (new array with an added element)

Here, the spread syntax (...) is used to create a new array newNumbers. Since the original numbers array remains unchanged, this is an immutable operation.

Benefits of Using Immutable Data Structures

Improved Predictability

Immutable data does not change, making unexpected modifications less likely and reducing the chance of bugs.

Compatibility with Libraries Based on Immutability

Libraries such as React and Redux are often designed based on immutable data, making state management easier when used appropriately.

Making Objects Immutable with Object.freeze

Object.freeze can be used to prevent modifications to an object.

1// Create a frozen object (properties cannot be modified)
2const person = Object.freeze({ name: "Alice", age: 25 });
3
4// Attempt to modify a property (ignored in non-strict mode, error in strict mode)
5person.age = 26;
6
7console.log(person); // { name: "Alice", age: 25 }

However, Object.freeze only performs a shallow freeze, meaning that properties of nested objects remain mutable.

1// Create a frozen object with a nested object
2const user: Readonly<{ profile: { name: string } }> = Object.freeze({
3    profile: { name: "Bob" }
4});
5
6// Attempt to modify a nested property (this works because Object.freeze() is shallow)
7user.profile.name = "Charlie"; // No TypeScript error, but still mutable
8
9console.log(user.profile.name); // "Charlie" (nested object is still mutable)

To create a completely immutable object, a deep freeze is required.

 1// Function to deeply freeze an object, making all nested objects immutable
 2function deepFreeze<T>(obj: T): Readonly<T> {
 3    Object.freeze(obj);
 4    Object.getOwnPropertyNames(obj).forEach(prop => {
 5        const value = (obj as any)[prop];
 6        if (typeof value === "object" && value !== null) {
 7            deepFreeze(value);
 8        }
 9    });
10    return obj;
11}
12
13// Create a deeply frozen object
14const user = deepFreeze({
15  profile: { name: "Bob" }
16});
17
18// Attempt to modify a nested property
19// (this will now throw an error in strict mode)
20user.profile.name = "Charlie";  // No TypeScript error, but modification is not allowed at runtime
21
22console.log(user.profile.name); // "Bob" (unchanged due to deep freeze)

Summary

  • Mutable data is modifiable, and includes objects and arrays.
  • Immutable data is unmodifiable, and includes primitive types such as strings and numbers.
  • Using spread syntax or methods like map, immutable data operations can be performed.
  • Object.freeze and deepFreeze can be used to prevent modifications to objects.
  • Using immutable data helps write more predictable and less error-prone code.

Adopting an immutable design improves code safety and readability, so make full use of it!

You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.

YouTube Video