Contents

Functional programming in Javascript - Immutability

After it's created don't change it!

Immutability is one the fundamental concept of functional programming (FP) and not only. Here we are going to see what it is about, why it is important and how it can be applied in JavaScript programming.

TL;DR

You cannot change an immutable data, you need to make a copy of that, then update the new one.

Functional Programming typically avoids using mutable state.

What does immutability mean?

When we talk about mutating the state we mean an alteration of the value of a variable or of the structure of an object.

Consider the following example

Let’s say we have a cart with some products

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const products = [
    {
        id: 1,
        price: 300
    },
    {
        id: 2,
        price: 100
    }
];
Now let's mutate the products

Let’s say we want to format the price of each product appending the Euro sign (€). We could iterate each product and update the price like the following:

1
2
3
products.forEach((product) => {
    product.price = `${product.price} €`
})

Now let’s try printing the price of a product

1
console.log(products[0].price) // 300 €

In the example above we have mutated the products and achieved our goal but we also broke the principle of immutability. Immutability means that once something is created it cannot be modified.

Why is immutability important?

At first glance the example above might look harmless but think about it in a more complex scenario, as applications in real life always are.

The modifications might not happen close to the scope were you are working on, they could even happen in different files so you would be totally unaware of those changes.

Now, to better understand why this is important, let’s forget we changed the prices and let’s say we need to calculate the total price of the cart. We are confident that we can achieve it by simply summing all the product prices but here is what we will get.

1
2
3
const reducer = (total, { price }) => total + price
const cartTotal = products.reduce(reducer)
console.log(cartTotal) // 0300 €100 €

Now you can imagine, as the application grows mutations can be problematic because it makes tracking the changes hard or even impossible.

In other words if we mutate the state, the data flow in your program is lossy. State history is abandoned, and strange bugs can creep your application.

How to avoid mutations

When data is immutable, its state cannot change after it’s created.

In other words you don’t change an existing object, instead, you create a new object and modify it.

Let’s get back to the previous example where we want to show the formatted prices.

Here is a better way to achieve the same result

Instead of changing the state of products we now create a new array of objects. The map function is perfect for this task.

1
2
3
4
const formattedProducts = products.map(product => ({
    ...product,
    price: `${product.price} €`
}))

Now can safely print the formatted prices and calculate the total because we can rely on the fact that products has its original state.

1
2
3
4
5
console.log(formattedProducts[0]) // 300 €

const reducer = (total, { price }) => total + price
const cartTotal = products.reduce(reducer)
console.log(cartTotal) // 400

In JavaScript , it’s important to understand the relationship between const and immutability.

const creates a variable name binding making it impossible to re-assign after its creation but it does not create immutable objects.

In fact, as shown in the example below, the mutation of the object is allowed.

1
2
3
4
5
const foo = { bar: true }

foo.bar = false

console.log(foo.bar) // false

You can make an object truly immutable by freezing the object. JavaScript has a method that freezes an object:

1
2
3
4
const foo = Object.freeze({ bar: true })

foo.bar = false
// Error: Cannot assign to read only property 'bar' of object Object

But frozen objects are only immutable one-level deep. For example, the following object is mutable:

1
2
3
4
5
6
7
const foo = Object.freeze({
    bar: { baz: true }
})

foo.bar.baz = false

console.log(foo.bar.baz) // false

As you can see the primitive properties of a frozen object cannot change but if the a property is an object it can be changed.

In order to make an object really immutable you need some external libraries. Immutable.js is among the most used libraries.