Immutability and you: Part 1
Alain D'Ettorre
Posted on July 9, 2020
The problem
Consider something like this
const a = [1,2,3];
const b = a;
a.push(4);
console.log(b); // [1,2,3,4]
Can you see the problem? You've created an array a
, then you copied (you think you copied) it into b
, so you legitimately think a
and b
are now separate. Then you mutate array a
by pushing a new value into it and then b
changes as well. Why is that?!
Something like this happened:
- A new array
[1,2,3]
is created inside a place in your RAM memory called the heap - You give this array a reference known as
a
, like an address, so that you can later fetch the array value from memory - You create a new reference
b
by assigning ita
, so now you have two references that point to the same position in memory (red flag!) - The
push
method just mutates the array value without changing its address, so when you outputb
you see the unexpected. OMG. And it's just 4 lines of code.
The same problem, but with an object
const question = { content: 'What is 6x9?' };
const answer = question;
answer.content = '42.';
console.log(question); // { content: '42.' }
When you change the content of the answer by mutating it, you change the question too (it's getting deep in here) since question
and answer
refer to the same value in memory.
The solution
So, how to solve the problem? Immutability!
If you think about primitives in JavaScript, like strings, numbers and booleans, you already know about immutability. Look at this
const a = 10;
const b = a;
// const a = a + 10; // You cannot reassign a const!
As you can see, there's no way to actually mutate the number a
and that's because primitives in JavaScript are immutable by default. Compound values, on the other hand, like arrays and objects, are mutable. For example, you can add values to arrays with push
or even re-assign object properties (as in the example above): the values have changed, of course, but the position in memory of the whole array or object is still the same so that every variable that points to that value shares the same value.
Instead of changing just a small part of an array or object, thus mutating it, you should replace the whole thing, even if you only changed a single letter. The idea is that values should be like photographs: they're immutable, they represent some point in time and you can take as many as you want, each different even if only by a bit. If you take a bad photo of your kitten doing something funny you just take a new shot.
Updating objects and arrays "immutably" (more on that later) takes a new memory slot in the heap and the old slot just gets automatically captured and erased by a thing called garbage collector. The advantage of this is that you avoid the reference hell like in the first example, you have better testing, predictability and even time-traveling debugging.
Immutability is a key concept of Redux, which is a popular state management pattern heavily used in React and in Angular too.
The spread syntax
The spread syntax, is, really, just a bunch of dots! It's the main operator you need to finally achieve immutability. What they do is expanding what immediately follows them, creating a copy of it. Let's rewrite the first example
const a = [1,2,3];
// const b = a; // <-- We changed this
const b = [...a]; // <-- Into this
a.push(4);
console.log(b); // [1,2,3]
What actually happened? Basically, [...a]
means "create a new array and replace ...a
with all the items inside a
", so you're effectively creating a real copy of a
which takes a new place in memory, not just a reference to it. That's why b
is now completely unaffected by whatever you do to a
.
In part 2 and 3 we'll take a look at how to update arrays and objects with the spread syntax in practice
Photo by Andrea Ferrario on Unsplash
Posted on July 9, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.