Mutation isn't always bad in JavaScript
Tags: javascript, react, webdev, node
Published: 2021-11-29 13:24:52

---
title: Mutation isn't always bad in JavaScript
published: true
description: We humans like dealing in absolutes. It's easy. Nuance is hard. Unfortunately for us, everything involves nuance.
tags: javascript, react, webdev, node
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/no5umh4x15krankemrbl.jpg
canonical_url: https://typeofnan.dev/mutation-isnt-always-bad-in-javascript/
---

We humans like dealing in absolutes. It's easy. Nuance is hard. Unfortunately for us, everything involves nuance. That's why we should question ourselves if we start to wonder if mutation is always bad.

---

Hey all, if you enjoy this article I’d love if you [signed up for my free, weekly newsletter](https://buttondown.email/devtuts). 1,500+ other developers have already signed up and are leveling up their web dev skills with me!

---

The truth is mutation isn't always bad, nor is it _usually_ bad. It just _is_. It's an excellent tool some languages give us to manipulate objects. Like with any tool, it's our responsibility to use it correctly.

# What is object mutation?

Here's a quick refresher on object mutation. Let's say we have a person object:

```javascript
const person = { name: 'Jarvis', age: 32 };
```

If we were to change this person's age, we will have mutated the object:

```javascript
person.age = 33;
```

This seems innocuous, right?

# Where mutation goes awry

Programming is all about _communication_ and _expectations_. Mutation goes awry when the intent of an operation isn't clearly communicated and when a developer's (or machine's) expectations are violated.

Let's consider the following (bad) use of mutation:

```javascript
function copyPerson(person, newName, newAge) {
  const newPerson = person;
  newPerson.name = newName;
  newPerson.age = newAge;
  return newPerson;
}
```

Why is this bad? Well let's look at what happens when we use this function in the wild:

```javascript
const jarvis = { name: 'Jarvis', age: 32, arms: 2, legs: 2 };
const stanley = copyPerson(jarvis, 'Stanley', 27);

console.log(stanley);
// { age: 27, arms: 2, legs: 2, name: "Stanley" }

console.log(jarvis);
// { age: 27, arms: 2, legs: 2, name: "Stanley" }
```

Out expectations have been thoroughly violated!

In our `copyPerson` function, we accidentally assigned `newPerson` a reference to the same `person` object. Since they reference the same object, mutating `newPerson` _also_ mutates `person`.

How do we fix this? We can do it entirely without mutation by copying the `person` object using the spread operator and simultaneously overwriting the `name` and `age` properties:

```javascript
function copyPerson(person, newName, newAge) {
  const newPerson = {
    ...person,
    name: newName,
    age: newAge,
  };
  return newPerson;
}
```

And that will work! But we can also make it work _with_ mutation, and this is totally fine. Some might even find it more readable!

```javascript
function copyPerson(person, newName, newAge) {
  const newPerson = { ...person };
  newPerson.name = newName;
  newPerson.age = newAge;
  return newPerson;
}
```

So wait, if this is fine, was mutation actually the culprit? No, it wasn't. _It was our lack of understanding about how references work_.

# Mutability and popular front-end frameworks

Popular front-end frameworks like React use references for render logic. Let's consider the following example:

```jsx
function App() {
  const [person, setPerson] = useState({ name: 'Jarvis', age: 32 });

  return <PersonCard person={person} />;
}
```

In this example, the `PersonCard` component will re-render if `person` changes.

Actually, let's be more careful in our wording here: the `PersonCard` component will re-render `person` _references a new object_. Again, we can get ourselves in trouble if we mutate `person` rather than creating a new object.

For this reason, the following code will be buggy:

```jsx
function App() {
  const [person, setPerson] = useState({ name: 'Jarvis', age: 32 });

  function incrementAge() {
    person.age++;
    setPerson(person);
  }

  return (
    <>
      <PersonCard person={person} />
      <button onClick={incrementAge}>Have a birthday</button>
    </>
  );
}
```

If we click the "Have a birthday" button, we increment the `age` property of the `person` object and then try to set the `person` state to that object. The problem is that it's not a _new_ object, it's the same `person` object as the prevous render! React's diffing algorithm sees no change to the `person` reference and doesn't re-render the `PersonCard`.

How do we fix this? You guessed it: we just have to make sure we create a new object based on `person`. Then, we can either accomplish the task by mutating the new object or some other means:

```jsx
function App() {
  const [person, setPerson] = useState({ name: 'Jarvis', age: 32 });

  function incrementAge() {
    const newPerson = { ...person };
    newPerson.age++;
    setPerson(newPerson);
  }

  return (
    <>
      <PersonCard person={person} />
      <button onClick={incrementAge}>Have a birthday</button>
    </>
  );
}
```

If your instinct here is that mutating `newPerson` is bad because we're using React, make sure to check your assumptions! There's nothing wrong here: `newPerson` is a variable scoped to the `incrementAge` function. We're not mutating something React is tracking, and therefore the fact that we're "in React" doesn't come into play here.

Again, it's very important to recognize here that mutation isn't bad. Our misunderstanding of object references and the React diffing algorithm are what caused the buggy behavior here.

# When is mutation good?

Now that I have discussed some scenarios in which mutation often gets blamed for buggy behavior, let's talk about when mutation really shines.

## Clarity

Often, I find mutation to be clearer. One example I like to use is if we need to create a new array with one of the elements in the array updated. When working in React, I have often seen the following:

```javascript
function updateItem(index, newValue) {
  const newItems = items.map((el, i) => {
    if (i === index) {
      return newValue;
    }
    return el;
  });
  setItems(newItems);
}
```

And this works fine, but it's kind of confusing and probably a bit challenging to read for someone who isn't fluent in JavaScript array methods.

A more readable alternative, in my opinion, is to simply create a copy of the initial array and then mutate the appropriate index of the copied array:

```javascript
function updateItem(index, newValue) {
  const newItems = [...items];
  newItems[index] = newValue;
  setItems(newItems);
}
```

I think that's _a lot_ clearer.

## Working with complex structures

One of my favorite examples of where mutability shines is building a tree structure. [You can do this in O(n) time](https://typeofnan.dev/an-easy-way-to-build-a-tree-with-object-references/) all thanks to references and mutation.

Consider the following array thay represents a flattened tree:

```javascript
const data = [
  { id: 56, parentId: 62 },
  { id: 81, parentId: 80 },
  { id: 74, parentId: null },
  { id: 76, parentId: 80 },
  { id: 63, parentId: 62 },
  { id: 80, parentId: 86 },
  { id: 87, parentId: 86 },
  { id: 62, parentId: 74 },
  { id: 86, parentId: 74 },
];
```

Each node has an `id` and then the `id` of its parent node (`parentId`). Our code to build a tree can be as follows:

```javascript
// Get array location of each ID
const idMapping = data.reduce((acc, el, i) => {
  acc[el.id] = i;
  return acc;
}, {});

let root;
data.forEach((el) => {
  // Handle the root element
  if (el.parentId === null) {
    root = el;
    return;
  }
  // Use our mapping to locate the parent element in our data array
  const parentEl = data[idMapping[el.parentId]];
  // Add our current el to its parent's `children` array
  parentEl.children = [...(parentEl.children || []), el];
});
```

How this works is we first loop through the `data` array once to create a mapping of where each element is in the array. Then, we do another pass through the `data` array and, for each element, we use the mapping to locate its parent in the array. Finally, we _mutate_ the parent's `children` property to add the current element to it.

If we `console.log(root)`, we end up with the full tree:

```
{
  id: 74,
  parentId: null,
  children: [
    {
      id: 62,
      parentId: 74,
      children: [{ id: 56, parentId: 62 }, { id: 63, parentId: 62 }],
    },
    {
      id: 86,
      parentId: 74,
      children: [
        {
          id: 80,
          parentId: 86,
          children: [{ id: 81, parentId: 80 }, { id: 76, parentId: 80 }],
        },
        { id: 87, parentId: 86 },
      ],
    },
  ],
};
```

That's really nifty and rather challenging to accomplish without mutation.

# Key takeaways about object mutation

Over time, I have come to realize that there are a few key points to understand with respect to mutation:

- Often we blame mutation for our own lack of understanding about how references work.
- Popular front-end frameworks like React rely on comparing object references for render logic. Mutating older versions of state causes all sorts of headaches and hard-to-understand bugs. Instead of recognizing the nuance, developers will often avoid mutation entirely anywhere within React code.
- Mutation is an _excellent_ tool when its usage is clearly communicated.
- Mutation is an _excellent_ tool if _localized_ (e.g., the mutated object never escapes a function).