Can .map() Mutate The Original Array? Yes.
Tags: javascript, beginners
Published: 2020-02-15 21:07:10

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:
```javascript
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)
```
```
// ♥ node map_examples.js 
originalArray =  [ 2, 3, 4 ]
mappedArray =  [ 4, 9, 16 ]
originalArray =  [ 2, 3, 4 ]
mappedArray =  [ 10, 9, 16 ]
```
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:
```javascript
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)
```
```JavaScript
// ♥ 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' }
]
```
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:
```javascript
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)
```
```
// ♥ 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' }
]
```
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:
```javascript
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)
```
```
// ♥ 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' }
]
```
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. 
```javascript
originalArray = [1, 2, 3];
mappedArray = originalArray.map((x, index)=> originalArray[index] = x * 2);

console.log('originalArray = ', originalArray)
console.log('mappedArray = ', mappedArray)
```
```
// ♥ node map_examples.js 
originalArray =  [ 2, 4, 6 ]
mappedArray =  [ 2, 4, 6 ]
```
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

- The Girl I Haven't Met - Kudasaibeats - [Spotify](https://open.spotify.com/track/00iwQgjIgoGO94BRBDyClo?si=0cb67_2yTq-oZ-OnGfBNUA)/[SoundCloud](https://soundcloud.com/kudasaibeats/the-girl-i-havent-met)

##References
[MDN Web Docs - .map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
[Old Map Cover Image](http://www.aljanh.net/old-nautical-map-wallpapers/177175900.html)