Why Is Immutability So Important (Or Needed) in JavaScript

Why is immutability so important (or needed) in JavaScript?

I have recently been researching the same topic. I'll do my best to answer your question(s) and try to share what I have learned so far.

The question is, why is immutability so important? What is wrong in
mutating objects? Doesn't it make things simple?

Basically it comes down to the fact that immutability increases predictability, performance (indirectly) and allows for mutation tracking.

Predictability

Mutation hides change, which create (unexpected) side effects, which can cause nasty bugs. When you enforce immutability you can keep your application architecture and mental model simple, which makes it easier to reason about your application.

Performance

Even though adding values to an immutable Object means that a new instance needs to be created where existing values need to be copied and new values need to be added to the new Object which cost memory, immutable Objects can make use of structural sharing to reduce memory overhead.

All updates return new values, but internally structures are shared to
drastically reduce memory usage (and GC thrashing). This means that if
you append to a vector with 1000 elements, it does not actually create
a new vector 1001-elements long. Most likely, internally only a few
small objects are allocated.

You can read more about this here.

Mutation Tracking

Besides reduced memory usage, immutability allows you to optimize your application by making use of reference- and value equality. This makes it really easy to see if anything has changed. For example a state change in a react component. You can use shouldComponentUpdate to check if the state is identical by comparing state Objects and prevent unnecessary rendering.
You can read more about this here.

Additional resources:

  • The Dao of Immutability
  • Immutable Data Structures and JavaScript
  • Immutability in JavaScript

If I set say an array of objects with a value initially. I can't
manipulate it. That's what immutability principle says, right?(Correct
me if I am wrong). But, what if I have a new News object that has to
be updated? In usual case, I could have just added the object to the
array. How do I achieve in this case? Delete the store & recreate it?
Isn't adding an object to the array a less expensive operation?

Yes this is correct. If you're confused on how to implement this in your application I would recommend you to look at how redux does this to get familiar with the core concepts, it helped me a lot.

I like to use Redux as an example because it embraces immutability. It has a single immutable state tree (referred to as store) where all state changes are explicit by dispatching actions which are processed by a reducer that accepts the previous state together with said actions (one at a time) and returns the next state of your application. You can read more about it's core principles here.

There is an excellent redux course on egghead.io where Dan Abramov, the author of redux, explains these principles as follows (I modified the code a bit to better fit the scenario):

import React from 'react';
import ReactDOM from 'react-dom';

// Reducer.
const news = (state=[], action) => {
switch(action.type) {
case 'ADD_NEWS_ITEM': {
return [ ...state, action.newsItem ];
}
default: {
return state;
}
}
};

// Store.
const createStore = (reducer) => {
let state;
let listeners = [];

const subscribe = (listener) => {
listeners.push(listener);

return () => {
listeners = listeners.filter(cb => cb !== listener);
};
};

const getState = () => state;

const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach( cb => cb() );
};

dispatch({});

return { subscribe, getState, dispatch };
};

// Initialize store with reducer.
const store = createStore(news);

// Component.
const News = React.createClass({
onAddNewsItem() {
const { newsTitle } = this.refs;

store.dispatch({
type: 'ADD_NEWS_ITEM',
newsItem: { title: newsTitle.value }
});
},

render() {
const { news } = this.props;

return (
<div>
<input ref="newsTitle" />
<button onClick={ this.onAddNewsItem }>add</button>
<ul>
{ news.map( ({ title }) => <li>{ title }</li>) }
</ul>
</div>
);
}
});

// Handler that will execute when the store dispatches.
const render = () => {
ReactDOM.render(
<News news={ store.getState() } />,
document.getElementById('news')
);
};

// Entry point.
store.subscribe(render);
render();

Also, these videos demonstrate in further detail how to achieve immutability for:

  • Arrays
  • Objects

Why is immutability so important (or needed) in JavaScript?

I have recently been researching the same topic. I'll do my best to answer your question(s) and try to share what I have learned so far.

The question is, why is immutability so important? What is wrong in
mutating objects? Doesn't it make things simple?

Basically it comes down to the fact that immutability increases predictability, performance (indirectly) and allows for mutation tracking.

Predictability

Mutation hides change, which create (unexpected) side effects, which can cause nasty bugs. When you enforce immutability you can keep your application architecture and mental model simple, which makes it easier to reason about your application.

Performance

Even though adding values to an immutable Object means that a new instance needs to be created where existing values need to be copied and new values need to be added to the new Object which cost memory, immutable Objects can make use of structural sharing to reduce memory overhead.

All updates return new values, but internally structures are shared to
drastically reduce memory usage (and GC thrashing). This means that if
you append to a vector with 1000 elements, it does not actually create
a new vector 1001-elements long. Most likely, internally only a few
small objects are allocated.

You can read more about this here.

Mutation Tracking

Besides reduced memory usage, immutability allows you to optimize your application by making use of reference- and value equality. This makes it really easy to see if anything has changed. For example a state change in a react component. You can use shouldComponentUpdate to check if the state is identical by comparing state Objects and prevent unnecessary rendering.
You can read more about this here.

Additional resources:

  • The Dao of Immutability
  • Immutable Data Structures and JavaScript
  • Immutability in JavaScript

If I set say an array of objects with a value initially. I can't
manipulate it. That's what immutability principle says, right?(Correct
me if I am wrong). But, what if I have a new News object that has to
be updated? In usual case, I could have just added the object to the
array. How do I achieve in this case? Delete the store & recreate it?
Isn't adding an object to the array a less expensive operation?

Yes this is correct. If you're confused on how to implement this in your application I would recommend you to look at how redux does this to get familiar with the core concepts, it helped me a lot.

I like to use Redux as an example because it embraces immutability. It has a single immutable state tree (referred to as store) where all state changes are explicit by dispatching actions which are processed by a reducer that accepts the previous state together with said actions (one at a time) and returns the next state of your application. You can read more about it's core principles here.

There is an excellent redux course on egghead.io where Dan Abramov, the author of redux, explains these principles as follows (I modified the code a bit to better fit the scenario):

import React from 'react';
import ReactDOM from 'react-dom';

// Reducer.
const news = (state=[], action) => {
switch(action.type) {
case 'ADD_NEWS_ITEM': {
return [ ...state, action.newsItem ];
}
default: {
return state;
}
}
};

// Store.
const createStore = (reducer) => {
let state;
let listeners = [];

const subscribe = (listener) => {
listeners.push(listener);

return () => {
listeners = listeners.filter(cb => cb !== listener);
};
};

const getState = () => state;

const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach( cb => cb() );
};

dispatch({});

return { subscribe, getState, dispatch };
};

// Initialize store with reducer.
const store = createStore(news);

// Component.
const News = React.createClass({
onAddNewsItem() {
const { newsTitle } = this.refs;

store.dispatch({
type: 'ADD_NEWS_ITEM',
newsItem: { title: newsTitle.value }
});
},

render() {
const { news } = this.props;

return (
<div>
<input ref="newsTitle" />
<button onClick={ this.onAddNewsItem }>add</button>
<ul>
{ news.map( ({ title }) => <li>{ title }</li>) }
</ul>
</div>
);
}
});

// Handler that will execute when the store dispatches.
const render = () => {
ReactDOM.render(
<News news={ store.getState() } />,
document.getElementById('news')
);
};

// Entry point.
store.subscribe(render);
render();

Also, these videos demonstrate in further detail how to achieve immutability for:

  • Arrays
  • Objects

Why is immutability so important (or needed) in JavaScript?

I have recently been researching the same topic. I'll do my best to answer your question(s) and try to share what I have learned so far.

The question is, why is immutability so important? What is wrong in
mutating objects? Doesn't it make things simple?

Basically it comes down to the fact that immutability increases predictability, performance (indirectly) and allows for mutation tracking.

Predictability

Mutation hides change, which create (unexpected) side effects, which can cause nasty bugs. When you enforce immutability you can keep your application architecture and mental model simple, which makes it easier to reason about your application.

Performance

Even though adding values to an immutable Object means that a new instance needs to be created where existing values need to be copied and new values need to be added to the new Object which cost memory, immutable Objects can make use of structural sharing to reduce memory overhead.

All updates return new values, but internally structures are shared to
drastically reduce memory usage (and GC thrashing). This means that if
you append to a vector with 1000 elements, it does not actually create
a new vector 1001-elements long. Most likely, internally only a few
small objects are allocated.

You can read more about this here.

Mutation Tracking

Besides reduced memory usage, immutability allows you to optimize your application by making use of reference- and value equality. This makes it really easy to see if anything has changed. For example a state change in a react component. You can use shouldComponentUpdate to check if the state is identical by comparing state Objects and prevent unnecessary rendering.
You can read more about this here.

Additional resources:

  • The Dao of Immutability
  • Immutable Data Structures and JavaScript
  • Immutability in JavaScript

If I set say an array of objects with a value initially. I can't
manipulate it. That's what immutability principle says, right?(Correct
me if I am wrong). But, what if I have a new News object that has to
be updated? In usual case, I could have just added the object to the
array. How do I achieve in this case? Delete the store & recreate it?
Isn't adding an object to the array a less expensive operation?

Yes this is correct. If you're confused on how to implement this in your application I would recommend you to look at how redux does this to get familiar with the core concepts, it helped me a lot.

I like to use Redux as an example because it embraces immutability. It has a single immutable state tree (referred to as store) where all state changes are explicit by dispatching actions which are processed by a reducer that accepts the previous state together with said actions (one at a time) and returns the next state of your application. You can read more about it's core principles here.

There is an excellent redux course on egghead.io where Dan Abramov, the author of redux, explains these principles as follows (I modified the code a bit to better fit the scenario):

import React from 'react';
import ReactDOM from 'react-dom';

// Reducer.
const news = (state=[], action) => {
switch(action.type) {
case 'ADD_NEWS_ITEM': {
return [ ...state, action.newsItem ];
}
default: {
return state;
}
}
};

// Store.
const createStore = (reducer) => {
let state;
let listeners = [];

const subscribe = (listener) => {
listeners.push(listener);

return () => {
listeners = listeners.filter(cb => cb !== listener);
};
};

const getState = () => state;

const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach( cb => cb() );
};

dispatch({});

return { subscribe, getState, dispatch };
};

// Initialize store with reducer.
const store = createStore(news);

// Component.
const News = React.createClass({
onAddNewsItem() {
const { newsTitle } = this.refs;

store.dispatch({
type: 'ADD_NEWS_ITEM',
newsItem: { title: newsTitle.value }
});
},

render() {
const { news } = this.props;

return (
<div>
<input ref="newsTitle" />
<button onClick={ this.onAddNewsItem }>add</button>
<ul>
{ news.map( ({ title }) => <li>{ title }</li>) }
</ul>
</div>
);
}
});

// Handler that will execute when the store dispatches.
const render = () => {
ReactDOM.render(
<News news={ store.getState() } />,
document.getElementById('news')
);
};

// Entry point.
store.subscribe(render);
render();

Also, these videos demonstrate in further detail how to achieve immutability for:

  • Arrays
  • Objects

React - What's the great benefit of having immutable props?

I'll keep it short as I am no expert in functional programming. I'm sure someone with far more experience will pitch in eventually.

Start off by thinking of your components, not in terms of Objects but instead, as functions (as in the mathematical term). Your component is a function of its properties. More specifically it's a render function. It takes props and returns HTML (actually it returns virtual dom trees, but that's irrelevant)

Functions in mathematics are pure. You give them an input, they give you an output. They do not have side effects and they do not use any other inputs. And that gives you some great benefits:

  1. Pure functions are predictable. Every input will have exactly the same output. Being predictable, means they can be optimized and utilize things like memoization to cache their result or not having to render parts of your UI if their props didn't change since you know they will not change.
  2. Depending only on the input you are given helps tremendously when trying to debug when something is wrong as you don't need to worry about global state. You only have to worry about the properties passed to you.
  3. You don't have to worry about the order that pure function are executed in, since they are guaranteed to have no side effects.
  4. It allows for really cool developer experience like time travel debugging and hot-swappable components.

Those are just some benefits, an average developer like myself can see. I'm sure someone with real functional programming experience can come with tons more. Hope it helps

React: Why is it necessary to have immutable props if values are passed as by value anyway?

In both examples you pass a string to the function. obj is an object, but obj.x is a string.

JavaScript treats strings and numbers (primitives) as immutable. So if you do:

var a = "bob";
var b = a;
b = "jack";

The original string a will be unchanged.

But objects are different. If you do:

var propsObj = { name: "bob" };
var newPropsObj = propsObj;
newPropsObj.name = "jack";

Then propsObj will also change, so propsObj is now { name: "jack" }.

React uses stuff you pass as props and state to make its virtual DOM. Because react uses these to see if anything changed, and render only stuff that did change, react relies on your code not to change props or state after you pass it to react.

In the second example, this will cause react to think that new and old props are still the same, so react will (incorrectly) conclude that it does not need to re-render.

What is immutability and why should I worry about it?

What is Immutability?

  • Immutability is applied primarily to objects (strings, arrays, a custom Animal class)
  • Typically, if there is an immutable version of a class, a mutable version is also available. For instance, Objective-C and Cocoa define both an NSString class (immutable) and an NSMutableString class.
  • If an object is immutable, it can't be changed after it is created (basically read-only). You could think of it as "only the constructor can change the object".

This doesn't directly have anything to do with user input; not even your code can change the value of an immutable object. However, you can always create a new immutable object to replace it. Here's a pseudocode example; note that in many languages you can simply do myString = "hello"; instead of using a constructor as I did below, but I included it for clarity:

String myString = new ImmutableString("hello");
myString.appendString(" world"); // Can't do this
myString.setValue("hello world"); // Can't do this
myString = new ImmutableString("hello world"); // OK

You mention "an object that just returns information"; this doesn't automatically make it a good candidate for immutability. Immutable objects tend to always return the same value that they were constructed with, so I'm inclined to say the current time wouldn't be ideal since that changes often. However, you could have a MomentOfTime class that is created with a specific timestamp and always returns that one timestamp in the future.

Benefits of Immutabilty

  • If you pass an object to another function/method, you shouldn't have to worry about whether that object will have the same value after the function returns. For instance:

    String myString = "HeLLo WoRLd";
    String lowercasedString = lowercase(myString);
    print myString + " was converted to " + lowercasedString;

    What if the implementation of lowercase() changed myString as it was creating a lowercase version? The third line wouldn't give you the result you wanted. Of course, a good lowercase() function wouldn't do this, but you're guaranteed this fact if myString is immutable. As such, immutable objects can help enforce good object-oriented programming practices.

  • It's easier to make an immutable object thread-safe

  • It potentially simplifies the implementation of the class (nice if you're the one writing the class)

State

If you were to take all of an object's instance variables and write down their values on paper, that would be the state of that object at that given moment. The state of the program is the state of all its objects at a given moment. State changes rapidly over time; a program needs to change state in order to continue running.

Immutable objects, however, have fixed state over time. Once created, the state of an immutable object doesn't change although the state of the program as a whole might. This makes it easier to keep track of what is happening (and see other benefits above).

How are people implementing immutable data structures in JavaScript when the language doesn't offer any obvious way of implementing immutability?

You are right, Javascript (unlike Haskell & co.) does not provide first-class support for Immutable Data Structures (In Java you'd have the keyword final). This does not mean that you cannot write your code, or reason about your programs, in an Immutable fashion.

As others mentioned, you still have some native javascript APIs that help you with immutability(ish), but as you realized already, none of them really solve the problem (Object.freeze only works shallowly, const prevents you from reassigning a variable, but not from mutating it, etc.).



So, how can you do Immutable JS?

I'd like to apologise in advance, as this answer could result primarily opinion based and be inevitably flawed by my own experience and way of thinking. So, please, pick the following with a pinch of salt as it just is my two cents on this topic.

I'd say that immutability is primarily a state of the mind, on top of which you can then build all the language APIs that support (or make easier to work with) it.

The reason why I say that "it's primarily a state of the mind" is because you can (kind of) compensate the lack of first-class language constructs with third party libraries (and there are some very impressive success stories).

But how Immutability works?

Well, the idea behind it is that any variable is treated as fixed and any mutation must resolve in a new instance, leaving the original input untouched.

The good news is that this is already the case for all the javascript primitives.

const input = 'Hello World';
const output = input.toUpperCase();

console.log(input === output); // false

So, the question is, how can we treat everything as it was a primitive?

...well, the answer is quite easy, embrace some of the basic principles of functional programming and let third party libraries fill those language gaps.

  1. Separate the state from their transition logic:
class User {
name;

setName(value) { this.name = value }
}

to just


const user = { name: 'Giuseppe' };

const setUserName = (name, user) => ({ ...user, name });

  1. Avoid imperative approaches and leverage 3rd party dedicated libraries
import * as R from 'ramda';

const user = {
name: 'Giuseppe',
address: {
city: 'London',
}
};

const setUserCity = R.assocPath(['address', 'city']);

const output = setUserCity('Verbicaro', user);

console.log(user === output); // recursively false

Perhaps a note on some of the libs I love

  1. Ramda provides immutability as well as enriching the js api with all those declarative goodies that you'd normally find in any f language (sanctuary-js and fp-ts are also great success stories)
  2. RxJS enables immutable and side-effects free programming with sequences while also providing lazy evaluation mechanisms, etc.
  3. Redux and XState provide a solution for immutable state management.

And a final example

const reducer = (user, { type, payload }) => {
switch(type) {
case 'user/address/city | set':
return R.assocPath(['address', 'city'], payload, user);

default:
return user;
}
}

const initial = {
name: 'Giuseppe',
address: {
city: 'Verbicaro',
},
};

const store = Redux.createStore(reducer, initial);
console.log('state', store.getState());

store.dispatch({
type: 'user/address/city | set',
payload: 'London',
});

console.log('state2', store.getState());
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.1.0/redux.js" integrity="sha512-tqb5l5obiKEPVwTQ5J8QJ1qYaLt+uoXe1tbMwQWl6gFCTJ5OMgulwIb3l2Lu7uBqdlzRf5yBOAuLL4+GkqbPPw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

What is the real benefit of Immutable Objects

The main problem concurrent updates of mutable data, is that threads may perceive variable values stemming from different versions, i.e. a mixture of old and new values when speaking of a single update, forming an inconsistent state, violating the invariants of these variables.

See, for example, Java’s ArrayList. It has an int field holding the current size and a reference to an array whose elements are references to the contained objects. The values of these variables have to fulfill certain invariants, e.g. if the size is non-zero, the array reference is never null and the array length is always greater or equal to the size. When seeing values of different updates for these variables, these invariants do not hold anymore, so threads may see a list contents which never existed in this form or fail with spurious exceptions, reporting an illegal state that should be impossible (like NullPointerException or ArrayIndexOutOfBoundeException).

Note that thread safe or concurrent data structures only solve the problem regarding the internals of the data structure, so operations do not fail with spurious exceptions anymore (regarding the collection’s state, we’ve not talked about the contained element’s state yet), but operations iterating over these collections or looking at more than one contained element in any form, are still subject to possibly observing an inconsistent state regarding the contained elements. This also applies to the check-then-act anti-pattern, where an application first checks for a condition (e.g. using contains), before acting upon it (like fetching, adding or removing an element), whereas the condition might change in-between.

In contrast, a thread working on an immutable data structure may work on an outdated version of it, but all variables belonging to that structure are consistent to each other, reflecting the same version. When performing an update, you don’t need to think about exclusion of other threads, it’s simply not necessary, as the new data structures are not seen by other threads. The entire task of publishing a new version reduces to the task of publishing the root reference to the new version of your data structure. If you can’t stop the other threads processing the old version, the worst thing that can happen, is that you may have to repeat the operation using the new data afterwards, in other words, just a performance issue, in the worst case.

This works smooth with programming languages with garbage collection, as these allow it to let the new data structure refer to old objects, just replacing the changed objects (and their parents), without needing to worry about which objects are still in use and which not.

Pros. / Cons. of Immutability vs. Mutability

Many functional languages are non pure (allow mutation and side effects).

f# is for example, and if you look at some of the very low level constructs in the collections you'll find that several use iteration under the hood and quite a few use some mutable state (if you want to take the first n elements of a sequence it's so much easier to have a counter for example).

The trick is that this is something to generally:

  1. Use sparingly
  2. Draw attention to when you do

    • note how in f# you must declare something to be mutable

That it is possible to largely avoid mutating state is evidenced by the large amount of functional code out there. For people brought up on imperative languages this is somewhat hard to get your head round, especially writing code previously in loops as recursive functions. Even trickier is then writing them, where possible, as tail recursive. Knowing how to do this is beneficial and can lead to far more expressive solutions that focus on the logic rather than the implementation. Good examples are those that deal with collections where the 'base cases' of having no, one or many elements are cleanly expressed rather than being part of the loop logic.

It is really 2 though that things are better. And this is best done via an example:

Take your code base and change every instance variable to readonly[1][2]. Change back only those ones where you need them to be mutable for your code to function (if you only set them once outside the constructor consider trying to make them arguments to the constructor rather than mutable via something like a property.

There are some code bases this will not work well with, gui/widget heavy code and some libraries (notably mutable collections) for example but I would say that most reasonable code will allow over 50% of all instance fields to be made readonly.

At this point you must ask yourself, "why is mutable the default?".
Mutable fields are in fact a complex aspect of your program as their interactions, even in a single threaded world, have far more scope for differing behaviour;



Related Topics



Leave a reply



Submit