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