DEV Community

Cover image for Can .map() Mutate The Original Array? Yes.
Joseph Trettevik
Joseph Trettevik

Posted on

Can .map() Mutate The Original Array? Yes.

To be fair, .map() is a non-destructive method. However, the callback function you pass to .map() can make it destructive.

Array.prototype.map()

First, let's do a quick review. The .map() method iterates over an array, passes each element to a given callback function, and then puts the return value in a new array at the element's index location. For example:

const originalArray = [2, 3, 4];
const mappedArray = originalArray.map(x => x ** 2);

console.log('originalArray = ', originalArray)
console.log('mappedArray = ', mappedArray)

mappedArray[0] = 10;

console.log('originalArray = ', originalArray)
console.log('mappedArray = ', mappedArray)
Enter fullscreen mode Exit fullscreen mode
// ♥ node map_examples.js 
originalArray =  [ 2, 3, 4 ]
mappedArray =  [ 4, 9, 16 ]
originalArray =  [ 2, 3, 4 ]
mappedArray =  [ 10, 9, 16 ]
Enter fullscreen mode Exit fullscreen mode

In this example we can see that mappedArray truly is a new array and not a new pointer to the original array. Not only does originalArray contain the same values after the .map() call, but it also remains unchanged after we set mappedArray at index 0 to 10.

Array.prototype.map() on an Array of Objects

However, what if .map() is called on is an array of objects? Let's say we want to copy of an array of objects and update one of the key value pairs. You also want the original array of objects to remain unchanged. Given our understanding that .map() is non-destructive, we may write our javascript solution something like this:

const objectsArray = [
    {'id': 1, 'name': 'Erik', 'yearsCompleted': 2, 'status': 'student'}, 
    {'id': 2, 'name': 'Carol', 'yearsCompleted': 1, 'status': 'student'}, 
    {'id': 3, 'name': 'Sarah', 'yearsCompleted': 4, 'status': 'student'}
];

const mappedObjectsArray = objectsArray.map(student => {
    if (student.yearsCompleted === 4) student.status = 'graduated';
    return student
})

console.log('objectsArray = ', objectsArray)
console.log('mappedObjectsArray = ', mappedObjectsArray)
Enter fullscreen mode Exit fullscreen mode
// ♥ node map_examples.js 
objectsArray =  [
  { id: 1, name: 'Erik', yearsCompleted: 2, status: 'student' },
  { id: 2, name: 'Carol', yearsCompleted: 1, status: 'student' },
  { id: 3, name: 'Sarah', yearsCompleted: 4, status: 'graduated' }
]
mappedObjectsArray =  [
  { id: 1, name: 'Erik', yearsCompleted: 2, status: 'student' },
  { id: 2, name: 'Carol', yearsCompleted: 1, status: 'student' },
  { id: 3, name: 'Sarah', yearsCompleted: 4, status: 'graduated' }
]
Enter fullscreen mode Exit fullscreen mode

Uhhh, that's not what we expect. Looking back at the code you might be thinking, "I know what we did wrong. The parameter points points to the actual object in objectsArray. We need to create a new object, modify it and return that new copy." Great idea. Let's try it:

const objectsArray = [
    {'id': 1, 'name': 'Erik', 'yearsCompleted': 2, 'status': 'student'}, 
    {'id': 2, 'name': 'Carol', 'yearsCompleted': 1, 'status': 'student'}, 
    {'id': 3, 'name': 'Sarah', 'yearsCompleted': 4, 'status': 'student'}
];

const mappedObjectsArray = objectsArray.map(student => student.yearsCompleted === 4 ? {...student, 'status': 'graduated'} : student)

console.log('objectsArray = ', objectsArray)
console.log('mappedObjectsArray = ', mappedObjectsArray)

mappedObjectsArray[0].status = 'sophmore';

console.log('objectsArray = ', objectsArray)
console.log('mappedObjectsArray = ', mappedObjectsArray)
Enter fullscreen mode Exit fullscreen mode
// ♥ node map_examples.js 
objectsArray =  [
  { id: 1, name: 'Erik', yearsCompleted: 2, status: 'student' },
  { id: 2, name: 'Carol', yearsCompleted: 1, status: 'student' },
  { id: 3, name: 'Sarah', yearsCompleted: 4, status: 'student' }
]
mappedObjectsArray =  [
  { id: 1, name: 'Erik', yearsCompleted: 2, status: 'student' },
  { id: 2, name: 'Carol', yearsCompleted: 1, status: 'student' },
  { id: 3, name: 'Sarah', yearsCompleted: 4, status: 'graduated' }
]
objectsArray =  [
  { id: 1, name: 'Erik', yearsCompleted: 2, status: 'sophmore' },
  { id: 2, name: 'Carol', yearsCompleted: 1, status: 'student' },
  { id: 3, name: 'Sarah', yearsCompleted: 4, status: 'student' }
]
mappedObjectsArray =  [
  { id: 1, name: 'Erik', yearsCompleted: 2, status: 'sophmore' },
  { id: 2, name: 'Carol', yearsCompleted: 1, status: 'student' },
  { id: 3, name: 'Sarah', yearsCompleted: 4, status: 'graduated' }
]
Enter fullscreen mode Exit fullscreen mode

Well that fixed our first unexpected behavior, but now we have a new one to deal with. Sarah's student record remained unchanged in the original array, but look at the original array after we modified Erik's status to 'sophmore' in mappedObjectsArray. Erik's status in objectsArray was also modified.

This is because javascript is not storing the actual object at each index of objectsArray, it stores a pointer that references the location in memory where the object is stored. So the .map() is creating a new array, but it is filling it with pointers to the same objects.

To fix this we need to instantiate new objects for every index location in mappedObjectsArray. The following shows an example of how to do this:

const objectsArray = [
    {'id': 1, 'name': 'Erik', 'yearsCompleted': 2, 'status': 'student'}, 
    {'id': 2, 'name': 'Carol', 'yearsCompleted': 1, 'status': 'student'}, 
    {'id': 3, 'name': 'Sarah', 'yearsCompleted': 4, 'status': 'student'}
];

const mappedObjectsArray = objectsArray.map(student => student.yearsCompleted === 4 ? {...student, 'status': 'graduated'} : {...student})

mappedObjectsArray[0].status = 'sophmore';

console.log('objectsArray = ', objectsArray)
console.log('mappedObjectsArray = ', mappedObjectsArray)
Enter fullscreen mode Exit fullscreen mode
// ♥ node map_examples.js 
objectsArray =  [
  { id: 1, name: 'Erik', yearsCompleted: 2, status: 'student' },
  { id: 2, name: 'Carol', yearsCompleted: 1, status: 'student' },
  { id: 3, name: 'Sarah', yearsCompleted: 4, status: 'student' }
]
mappedObjectsArray =  [
  { id: 1, name: 'Erik', yearsCompleted: 2, status: 'sophmore' },
  { id: 2, name: 'Carol', yearsCompleted: 1, status: 'student' },
  { id: 3, name: 'Sarah', yearsCompleted: 4, status: 'graduated' }
]
Enter fullscreen mode Exit fullscreen mode

Now we're getting the behavior that we expected on our first attempt. Because we created new objects for each index location of mappedObjectsArray, when we modified these new objects, the objects in objectsArray remain unchanged.

Overtly Destructive Callback Functions

Let's look at one more example to solidify the idea that the callback function you pass in can make .map() destructive.

originalArray = [1, 2, 3];
mappedArray = originalArray.map((x, index)=> originalArray[index] = x * 2);

console.log('originalArray = ', originalArray)
console.log('mappedArray = ', mappedArray)
Enter fullscreen mode Exit fullscreen mode
// ♥ node map_examples.js 
originalArray =  [ 2, 4, 6 ]
mappedArray =  [ 2, 4, 6 ]
Enter fullscreen mode Exit fullscreen mode

In this example, the callback function is assigning new values at each index location of originalArray. Obviously, if you wanted to explicit update the original array like this, you probably wouldn't use .map(). However, its interesting to note that .map() does not complain about the callback function modifying the array on which it was called.

Conclusion

Things to consider when using .map():

  • Write your callback function carefully because it can modify the original array.
  • In your callback function, always create new objects for every object in the original array. Otherwise you will just be copying pointers to the original objects.

Song of the Week

References

MDN Web Docs - .map()
Old Map Cover Image

Top comments (4)

Collapse
 
caiangums profile image
Ilê Caian • Edited

I hardly disagree with the title of this article. For me, the biggest point here is: Array.prototype.map() does not mutate by itself the original array.

It can change the original array if you write it in a way to do so. But the callback function is up to the developer, not up to specification and the specification says that the .map() doesn't mutate.

Another good point of discussion is why the following code works the way it works:

const originalArray = [1, 2, 3]
const mappedArray = originalArray.map((x, index)=> originalArray[index] = x * 2)

console.log('originalArray = ', originalArray) // --> originalArray = [2, 4, 6]
console.log('mappedArray = ', mappedArray) // --> mappedArray = [2, 4, 6]

By writing the arrow function with this shorthand it makes the same as

const mappedArray = originalArray.map((element, index)=> {
  originalArray[index] = element * 2
  return originalArray[index]
})

and if the arrow function is changed the like

const mappedArray = originalArray.map((element, index)=> {
  originalArray[index] = element * 2
  return element
})

then the mappedArray should be equal to the originalArray before those changes. Just check the following code:

const originalArray = [1, 2, 3]

const mappedArray = originalArray.map((element, index)=> {
  originalArray[index] = element * 2
  return element
})

console.log('originalArray = ', originalArray) // --> originalArray = [2, 4, 6]
console.log('mappedArray = ', mappedArray) // --> mappedArray = [1, 2, 3]
Collapse
 
lofiandcode profile image
Joseph Trettevik • Edited

Thank you so much for the feedback. I’m always trying to learn more, so I appreciate you taking the time to read and comment on my blog post.

You’re absolutely right that Array.prototype.map() will not mutate the original array by itself, which is why that’s the first thing I mentioned at the beginning of this post.

Now was my title for this post a little on the clickbaity side of things? Sure. But my point was that if we’re not thoughtful in the way we write the callback function, Array.prototype.map() can mutate the original array.

This post is intended for JavaScript beginners, and if as a beginner you believe that .map() will never mutate the original array, you’re going to run into this issue and have no idea why your original array is getting mutated.

I think we agree on the subject, we just disagree on the semantics.

You’re essentially saying that Array.prototype.map() doesn’t mutate the original array, developers who use Array.prototype.map() mutate the original array.

And you’re right, but at the end of the day Array.prototype.map() was executed and the original array got mutated.

Array.prototype.map() never executes itself. There is always a developer behind it.

Finally, thanks for pointing out how you can rewrite the arrow function in my last example to get a different behavior. That’s pretty cool and I hadn’t thought of that!

Collapse
 
andreaself profile image
Andrea Self

Your article was so helpful to me. Thanks for the clear explanation and examples. Helped me solve a re-rendering issue in React.

Collapse
 
vincenzomann profile image
Vincenzo Mann

Thank you! This was driving me mad