Avoiding Mutations in JavaScript with Immutable Data Structures

Share this video with your friends

Send Tweet

This lesson teaches you the concepts of immutability, the difference between mutable and immutable data structures, and how to change data through mutable and immutable patterns. Immutable data is necessary in functional programming because mutations are a side effect. Our transformation of data should not affect the original data source, but instead should return a new data source with our updates applied.

To demonstrate the difference between mutability and immutability, imagine taking a drink from a glass of water. If our glass is mutable, when we take a drink, we retain the same glass and change the amount of water in that glass. However, if our glass is immutable, when we take a drink, we get back a brand new, identical glass containing the correctly drank amount. Perhaps a strange way to conceive of the action, but creating new data structures makes our methods pure and thread-safe, a benefit of functional programming.

Phillip
Phillip
~ 6 years ago

1:43 might be misunderstood when you say using spread gives you a clone. Yes, the effect of your code is you're making a clone, but one a should probably not think the spread operator means "clone". Rather "pull out all the contents". HTH

JP Lew
JP Lew
~ 6 years ago

Running Node 10 but I got a different result from you.

const push = val => array => [...array].push(val)
console.log(push(4)([1, 2, 3])) // 4

This returns the number 4, not the actual array.

const push = val => array => {
  const newArr = [...array]
  newArr.push(val)
  return newArr
}
console.log(push(4)([1, 2, 3])) // [1,2,3,4]

This is the only way I could return an array.

Correct me if I'm wrong, but the reason a === b is failing in the video is because we're comparing [1,2,3] === 4

Kyle Shevlin
Kyle Shevlin(instructor)
~ 6 years ago

Hey JP, good catch, I'll need to update this video. Push returns the number of elements in the array, so this is a bug on my part. Thanks for finding this.

JP Lew
JP Lew
~ 6 years ago

no prob. A bit pedantic on my part though, because your underlying point about immutability is still clear. Really enjoying the lessons so far, thanks!

Kyle Shevlin
Kyle Shevlin(instructor)
~ 6 years ago

Hey JP, video is fixed. 👍

Tony Catalfo
Tony Catalfo
~ 6 years ago

Could you give an example of how you could do this if you have an array of nested objects or a multi-dimensional array?

Kyle Shevlin
Kyle Shevlin(instructor)
~ 6 years ago

The concept is the same no matter the data structure. Whenever you make an update return a new data structure with the correct changes, leaving the original intact. I wrote this example in about a minute:

const updateObject = updater => obj => updater(obj)
const decrementValue = key => obj => ({
  ...obj,
  [key]: obj[key] - 1
})
const decrementTime = decrementValue('time')
const updateTimer = updateObject(decrementTime)
const timerFactory = time => ({
  time
})
const timers = [
  timerFactory(60),
  timerFactory(30),
  timerFactory(15)
]
const updatedTimers = timers.map(updateTimer)
console.log(updatedTimers) // [{ time: 59 }, { time: 29 }, { time: 14 }]

Every time we decrement the time on a timer, we return a new object by spreading the previous key/value pairs into a new object along with our change. Array.prototype.map() is an immutable method so we're getting a new array. So our updatedTimers is a brand new array with brand new objects and our originals are intact.

Tony Catalfo
Tony Catalfo
~ 6 years ago

are you overwriting push?

Kyle Shevlin
Kyle Shevlin(instructor)
~ 6 years ago

I'm not sure what you're asking. I never overwrite or monkey patch push in the lesson. I'm demonstrating how we can return a new array when we "push" an item onto our array. The function receives an array, makes a copy, pushes a value onto the copy and returns the copy.

A non-code metaphor might help perhaps. Imagine you wrote a paper for an class and you give it to your teacher to give you notes and edits. If the teacher makes their edits with mutations, they would hand you back the original paper with all of their edits on it, red marks here and there, and the original would be forever changed.

On the other hand, if the teacher made their edits immutably, they would make a fresh copy of your paper, and make their edits on the copy and give that to you. You could ask for the original back and nothing will have changed about it.

Tony Catalfo
Tony Catalfo
~ 6 years ago

clone.push(value) confused me. I understand it now. Thank you for the explanation.

Pavol
Pavol
~ 6 years ago

We're going to assign a variable B to A

You're assigning a value to a variable, not otherwise right?

Pavol
Pavol
~ 6 years ago

We can see this by using a strictly equals equality check, because it will check the references are the same, not that the values are the same.

This is not entirely true:

You should take special note of the == and === comparison rules if you're comparing two non-primitive values, like objects (including function and array). Because those values are actually held by reference, both == and === comparisons will simply check whether the references match, not anything about the underlying values. You-Dont-Know-JS/ch2.md at master · getify/You-Dont-Know-JS

Kyle Shevlin
Kyle Shevlin(instructor)
~ 6 years ago

Hi Pavol,

In the first case, I'm creating a variable b and assigning it to whatever the variable a is assigned to.

In the second case, you are correct that both == and === will tell us if the references are different. However, the point of the lesson is immutable data structures, not loose equality and coercion. My statement that a strict equality check will determine if two references are different is accurate. If I had added a note stating that one could use loose equality to achieve the same thing, it would have distracted from the lesson at hand.

Panks A
Panks A
~ 6 years ago

Hi Kyle,

Can you share how to interpret const push = val => array => { there are two arrow function used. Is this a nested function?

NHI TRAN
NHI TRAN
~ 5 years ago

is it better to make things mutable/immutable? Or does it depend on the use case? When it comes to making "pure" functions should all methods return immutable values? or was this video more on the concepts around mutable and immutable

Mark Waterous
Mark Waterous
~ 5 years ago

Can you share how to interpret const push = val => array => { there are two arrow function used. Is this a nested function?

I was unfamiliar with this syntax as well so I went down the rabbit hole. For anyone else who's being introduced to it for the first time, it's called a curried function and turns out there's a good video about it here on Egghead: https://egghead.io/lessons/javascript-currying-with-examples

Here's the initial stack overflow answer I found when searching: https://stackoverflow.com/questions/32782922/what-do-multiple-arrow-functions-mean-in-javascript

Kyle Shevlin
Kyle Shevlin(instructor)
~ 5 years ago

Hey Mark, I explain currying and curried functions in the very next lesson in the course. No need to go so far for an explanation.

It’s also pretty straight forward if you think through it for a moment. An arrow => implicitly returns any expression. This is why we need {} to create a function body and ({}) to implicitly return an object. Thus, we can see that this:

const add = x => y => x + y

Is the same as:

const add = function(x) { return function(y) { return x + y } }

Mark Waterous
Mark Waterous
~ 5 years ago

Thanks Kyle! Excellent explanation aside, what you're ultimately saying is maybe I should watch the entire course before I wander off trying to answer questions the course already covers? Reading that loud and clear :D

Kyle Shevlin
Kyle Shevlin(instructor)
~ 5 years ago

Ha, I’m saying I won’t leave you hanging in the course 😁 hope you enjoy the rest of it.

Serban Stancu
Serban Stancu
~ 4 years ago

I think it would be worth mentioning that spreading is not doing a deep clone. Using the spread operator on an array of objects won't give you the result you would expect.