JavaScript Array Methods: Mutating vs. Non-Mutating

JavaScript offers several ways to add, remove, and replace items in an array – but some of these ways mutate the array, and others are non-mutating; they produce a new array.

Below, I have outlined how to accomplish these three tasks using both mutating and non-mutating practices. This post concludes by showing how to iterate over an array and transform each item using the non-mutating array.map().

Although I don't present an exhaustive list here, below are strategies to accomplish virtually any basic array manipulation.

NOTE: Below I assign an array to a const when I use non-mutating methods, and let for mutating ones. Although you can mutate an array assigned to a const without throwing an error, I use const as a signal to other developers that the assigned value will not change.

CAUTION: As you read this post, take special note of the differences between:

  1. array.splice() which mutates the original array, and
  2. array.slice() which does not mutate the original array

I. Add: Mutating

The mutating methods for adding to an array are array.push() and array.ushift().

// since the array will be mutated, 
// use 'let' rather than 'const'
let mutatingAdd = ['a', 'b', 'c', 'd', 'e']; 

mutatingAdd.push('f'); // ['a', 'b', 'c', 'd', 'e', 'f']  
mutatingAdd.unshift('z'); // ['z', 'b', 'c', 'd', 'e' 'f']  

The code above illustrates that:

  • array.push() adds an item to the end of the array
  • array.unshift() adds an item to the beginning of the array.

II. Add: Non-Mutating

There are two ways to add new items to an array without mutating the original array.

First, there is array.concat().

// since we will not be mutating, 
// use const
const arr1 = ['a', 'b', 'c', 'd', 'e'];

const arr2 = arr1.concat('f'); // ['a', 'b', 'c', 'd', 'e'. 'f']  
console.log(arr1); // ['a', 'b', 'c', 'd', 'e']  

The second way to add to an array without mutating the original involves using JavaScript's spread operator. The spread operator is three dots (...) preceding an array.

// since we will not be mutating, 
// use const
const arr1 = ['a', 'b', 'c', 'd', 'e'];

const arr2 = [...arr1, 'f']; // ['a', 'b', 'c', 'd', 'e', 'f']  
const arr3 = ['z', ...arr1]; // ['z', 'a', 'b', 'c', 'd', 'e']  

The spread operator, when used as above, will copy the original array, take all the elements out of the array, and deposit the elements in the new context.

On line 5 above, we take copies of all the elements from arr1, put them in a new array, and add 'f' to the end.

On line 6, the same thing occurs but the new item, 'z', is placed before the other array items.

III. Remove: Mutating

The mutating methods for removing from an array are array.pop() and array.shift().

// since the array will be mutated, 
// use 'let' rather than 'const'
let mutatingRemove = ['a', 'b', 'c', 'd', 'e'];  
mutatingRemove.pop(); // ['a', 'b', 'c', 'd']  
mutatingRemove.shift(); // ['b', 'c', 'd']  

The code above illustrates that:

  • array.pop() removes an item at the end of the array
  • array.shift() removes an item at the beginning of the array.

array.pop() and array.shift() return the item that is removed. This means you can 'catch' the deleted item in a variable.

let mutatingRemove = ['a', 'b', 'c', 'd', 'e'];

const returnedValue1 = mutatingRemove.pop();  
console.log(mutatingRemove); // ['a', 'b', 'c', 'd']  
console.log(returnedValue1); // 'e'

const returnedValue2 = mutatingRemove.shift();  
console.log(mutatingRemove); // ['b', 'c', 'd']  
console.log(returnedValue2); // 'a'  

There is also array.splice() for removing items from an array.

let mutatingRemove = ['a', 'b', 'c', 'd', 'e'];  
mutatingRemove.splice(0, 2); // ['c', 'd', 'e']  

mutatingRemove.splice(0, 2) above takes two parameters (it can take more than two, more on that below).

  1. The first parameter is the starting point of the splice.
  2. The second parameter is the number of items to remove from the array.

In the example above, two items are removed from the mutatingRemove array (second argument), starting from the index of 0 (the first argument).

Like array.pop() and array.shift(), array.splice() returns the items it removes.

let mutatingRemove = ['a', 'b', 'c', 'd', 'e'];  
let returnedItems = mutatingRemove.splice(0, 2);  
console.log(mutatingRemove); // ['c', 'd', 'e']  
console.log(returnedItems) // ['a', 'b']  

IV. Remove: Non-Mutating

JavaScript's array.filter() method creates a new array from an original array, but the new array only contains items that match the specified criteria.

// since we will not be mutating, 
// use const
const arr1 = ['a', 'b', 'c', 'd', 'e'];

const arr2 = arr1.filter(a => a !== 'e'); // ['a', 'b', 'd', 'f']  
// OR
const arr2 = arr1.filter(a => {  
  return a !== 'e';
}); // ['a', 'b', 'd', 'f']

In the code above, the criterion is "is not an 'e'", so a new array (arr2) is created that is just like the original, but only the items that that meet the "is not an 'e'" criterion.


*Some peculiarities of arrow functions:

With single-line arrow functions (line 5), the 'return' keyword is implicit so you don't need to type it out.

However, on a multi-line arrow function (lines 7-9) you need to explicitly return a value.


Another way to remove items from an array in a non-mutating manner is through the use of array.slice(). (Not to be confused with array.splice())

array.slice() takes two parameters.

  1. The first parameter is the index where the copy should begin.
  2. The second parameter is the index where the copy should end. This end index is non-inclusive.
// since we will not be mutating, 
// use const
const arr1 = ['a', 'b', 'c', 'd', 'e'];  
const arr2 = arr1.slice(1, 5) // ['b', 'c', 'd', 'e']  
const arr3 = arr1.slice(2) // ['c', 'd', 'e']  

On line 4 above (const arr2 = arr1.slice(1, 5)), arr2 is created from copying arr1 staring at the index of 1 and ending at the index before 5 (i.e. index 4).

Line 5 (const arr3 = arr1.slice(2)) shows a handy trick. If the second parameter of array.slice() is not provided, the method makes a copy from the beginning index to the end of the array.

V. Replace: Mutating

If you know the index of the item you want to replace, you can use array.splice() to replace it with something else.

In order to do this, we need to use at least three parameters:

  1. The first parameter is the index to start replacing.
  2. The second parameter is the number of items to remove.
  3. The third and all other parameters are what will be inserted into the array.
// since the array will be mutated, 
// use 'let' rather than 'const'
let mutatingReplace = ['a', 'b', 'c', 'd', 'e'];  
mutatingReplace.splice(2, 1, 30); // ['a', 'b', 30, 'd', 'e']  
// OR
mutatingReplace.splice(2, 1, 30, 31); // ['a', 'b', 30, 31, 'd', 'e']  

Line 4 (mutatingReplace.splice(2, 1, 30)) replaces 'c' with 30.
Line 6 (mutatingReplace.splice(2, 1, 30, 31)) removes 'c' and adds both 30 and 31.

VI. Replace: Non-Mutating

We can use array.map() to create a new array, but we can also check each item and replace items that match a specified criterion.

// since we will not be mutating, 
// use const
const arr1 = ['a', 'b', 'c', 'd', 'e']  
const arr2 = arr1.map(item => {  
  if(item === 'c') {
    item = 'CAT';
  }
  return item;
}); // ['a', 'b', 'CAT', 'd', 'e']

The code above creates a new array based on arr1, but replaces all 'c's with CATs.

Transforming Data with array.map()

array.map() is a powerful method, and can be used to transform data without compromising the integrity of the original data set.

// since we will not be mutating, 
// use const
const origArr = ['a', 'b', 'c', 'd', 'e'];  
const transformedArr = origArr.map(n => n + 'Hi!'); // ['aHi!', 'bHi!', 'cHi!', 'dHi!', 'eHi!']  
// OR
const transformedArr = origArr.map(n => {  
  return n * 2;
})// ['aHi!', 'bHi!', 'cHi!', 'dHi!', 'eHi!']
console.log(origArr); // ['a', 'b', 'c', 'd', 'e']; // orignal array is intact  

 

If you'd like to be notified when I publish new content, sign up for my mailing list in the navbar.