DEV Community

Cover image for To Mutate, Or Not To Mutate? JavaScript Array Methods Cheat Sheet
Annie Liao
Annie Liao

Posted on • Updated on

To Mutate, Or Not To Mutate? JavaScript Array Methods Cheat Sheet

When you start learning different ways to manipulate an array, your world is lit up. You can add and delete elements! You can reverse them! Then you realize that not all built-in array methods behave the way you want them to. Somewhere along the way, you get confused as to which method alters your original array, and which one doesn’t.

Wouldn’t it be nice to have a tool that tells you if a particular array method mutates the original array or not?

Yes, I know. We can always check MDN documentation. Better yet, there’s this awesome website that lists (hopefully) all commonly-used array methods and identifies them as “mutates” or “doesn’t mutate”.

I did a quick tally and found that out of the 31 methods listed on the site, only 9 of them mutate the array.

Here's an idea:

Why not just remember the 9 destructive array methods? That way, we need not pause too long when it comes to the manipulation of arrays. If we call any of the 9 methods, the original array will be modified for sure.

Alright, let's explore these 9 methods and watch the mutators mutate!

#1 .push()

The .push() method takes in an array element, which will be added to the end of the array.

Here we have an array called "happy", consisting of 3 happyface emojis. After we push a poop face, the "happy" array now has a happy poopy face attached to the end.

let happy = ['🙂', '😀', '😊'];

happy.push('💩');

happy
// => [ '🙂', '😀', '😊', '💩']
Enter fullscreen mode Exit fullscreen mode

#2 .pop()

This method is similar to .push() method, as both involve changing the last item of the array.

Contrary to .push() method, .pop() will remove the last item of the array. As shown below, we just .pop() the poop out of the happy array!

happy
// => [ '🙂', '😀', '😊', '💩']

happy.pop();

happy
// => [ '🙂', '😀', '😊']
Enter fullscreen mode Exit fullscreen mode

👉Tip: The poop emoji was not randomly chosen. It is here to help us remember that both .push() and .pop() mutate the array by adding (pushing) or removing (popping) the last item (poop).

#3 .unshift()

The .unshift() method works similarly to .push(), except that it adds a new item to the beginning of the array.

Back to our clean, poop-free happyface example. After we pass in a ghost emoji onto the .unshift() method, the happy array is now led by a ghost!

let happy = [ '🙂', '😀', '😊']

happy.unshift('👻');

happy
// => [ '👻', '🙂', '😀', '😊']
Enter fullscreen mode Exit fullscreen mode

Now, what if we want to remove the first item?

#4 .shift()

Whereas .pop() removes the last item, .shift() simply removes the first item in an array, like so:

happy
// => [ '👻', '🙂', '😀', '😊']

happy.shift();

happy
// => ['🙂', '😀', '😊']
Enter fullscreen mode Exit fullscreen mode

👉Tip: As you might have noticed by now, .unshift() and .shift() are mirroring methods that add/remove the first item in an array. By the same token, you can think of .push() and .pop() as another set of methods that add/remove the last item of an array.

#5 .reverse()

This one is a no-brainer. As its name suggests, the .reverse() method reverses the order of the elements in an array. Here, we can see a waxing and waning moon effect thanks to the .reverse() method.

let moon = ['🌒', '🌓', '🌔', '🌕'];

moon.reverse();

moon
// => [ '🌕', '🌔', '🌓', '🌒']
Enter fullscreen mode Exit fullscreen mode

#6 .splice()

The .splice() method is powerful, as it can take as many arguments as you want and mutate the array by adding and/or replacing item(s) within the array.

In the three-wise-monkeys array example below, we implement the .splice() method by passing in 3 types of arguments:

(1) the index at which the change starts (index 1)
(2) number of items to remove (1 item: "hear-no-evil" monkey)
(3) item(s) to add to the array (3 evil faces)

let monkeys = ['🙈', '🙉', '🙊'];

monkeys.splice(1, 1, '😈', '😈', '😈');
Enter fullscreen mode Exit fullscreen mode

Now the "hear-no-evil" monkey is gone and replaced by 3 evil faces 😱

monkeys
// => [ '🙈', '😈', '😈', '😈', '🙊']
Enter fullscreen mode Exit fullscreen mode

You can also choose to only add items to a specific location inside the array. Here, we inserted the evil face at index 2 but kept all three monkeys (by passing in 0 as the second argument).

let monkeys = ['🙈', '🙉', '🙊'];

monkeys.splice(2, 0, '😈');

monkeys
// => [ '🙈', '🙉', '😈', '🙊']
Enter fullscreen mode Exit fullscreen mode

👉Tip: Watch out for the .slice() method. It looks like .splice() on the surface, but .slice() method produces a new array and hence does NOT mutate the original array.

#7 .sort()

This one is a doozy. The .sort() method does sort the items in the array in descending order by default, but it doesn't necessarily behave the way we commoners expect.

Let's say we have an array of numbers. What happens if we apply the .sort() method?

let numbers = [3, 25, 99];

numbers.sort();

numbers
// => [ 25, 3, 99 ]
Enter fullscreen mode Exit fullscreen mode

Weird, right?

As it turns out, the .sort() method converts each item into a string and compares them according to their Unicode code points.

We can check the Unicode code points of the first value of a string by calling the .codePointAt() method.

After coercing these numbers into strings, we see why .sort() considers "25" less than "3":

"3".codePointAt(0) 
// => 51
"25".codePointAt(0) 
// => 50
"99".codePointAt(0) 
// => 57
Enter fullscreen mode Exit fullscreen mode

Let's not forget that emojis are also passed in as strings!

let madness = ["🤬", "😠", "😡"]
madness.sort();
madness
// => [ '😠', '😡', '🤬' ]

"😠".codePointAt(0) 
// => 128544
"😡".codePointAt(0) 
// => 128545
"🤬".codePointAt(0) 
// => 129324

Enter fullscreen mode Exit fullscreen mode

To top it off:

let mixMadness = [3, "🤬", 25, "😠", "😡", "wtf"]
mixMadness.sort();
mixMadness
// => [ 25, 3, 'wtf', '😠', '😡', '🤬' ]

/*** behind the scenes ***/
"25".codePointAt(0) 
// => 50
"3".codePointAt(0) 
// => 51
"wtf".codePointAt(0) 
// => 119

"😠".codePointAt(0) 
// => 128544
"😡".codePointAt(0) 
// => 128545
"🤬".codePointAt(0) 
// => 129324

Enter fullscreen mode Exit fullscreen mode

If you really want to sort the numbers in an order that makes human sense, you can pass in a compare function as an argument in the .sort() method:

let numbers = [25, 99, 3];

numbers.sort(function(num1, num2) {
  return num1 - num2;
});

numbers
// => [ 3, 25, 99 ]
Enter fullscreen mode Exit fullscreen mode

In the code snippet above, the .sort() method takes in a function that compares two adjacent numbers in the array. Here's a breakdown of what happens:

(1) if the result of (num1 - num2) is less than 0, num1 will precede num2.
(2) if the result of (num1 - num2) is greater than 0, num2 will take precedence.
(3) if the result of (num1 - num2) is 0, the order of num1 and num2 will remain the same.

(big thanks to @jamiemccarville for the explainer reminder!)

And if your head hasn't exploded yet, check out this amazing article that dives deep into the .sort() method: What I Talk About When I Talk About Sorting: Untangling Array#sort

#8 .copyWithin()

As the name suggests, this method copies one part of the array and put it on another part within the same array.

It takes in 3 arguments:

(1) the index at which to put copied item(s)
(2) (optional) the index at which to start copying item(s) from (inclusive)
(3) (optional) the index at which to end copying item(s) from (exclusive)

To illustrate, here's how we can utilize the .copyWithin() method:

let okHands = ["👌", "", "🤙", "🙏"]
okHands.copyWithin(2, 0, 1)
/* arg 1: put copied item at index 2, which is currently "🤙" */
/* arg 2 & 3: copy the item between index 0 (inclusive) and index 1 (exclusive), which is currently "👌"

// => [ '👌', '✋', '👌', '🙏' ]
Enter fullscreen mode Exit fullscreen mode

Here's another example to help clarify the purpose of each argument:

okHands = ["👌", "", "🤙", "🙏"]
okHands.copyWithin(2, 0, 2)
/* arg 1: put copied item at index 2, which is currently "🤙" */
/* arg 2 & 3: copy the item between index 0 (inclusive) and index 2 (exclusive), which are currently "👌", "✋"

// => [ '👌', '✋', '👌', '✋' ]
Enter fullscreen mode Exit fullscreen mode

Notice how the last item ("🙏") was replaced after we copied two items ("👌", "✋").

While I haven't had an opportunity to use .copywithin() in my program, this method appears to function similarly to .splice(). Based on the types of arguments both methods take in, the latter offers more flexibility because we can insert new items instead of copying from within. So, pick your poison.

#9 .fill()

Lastly, the .fill() method changes some or all items in the array into the value being passed in.

It takes in 3 arguments as well:
(1) a value to fill the array with
(2) (optional) the starting index (inclusive)
(3) (optional) the ending index (exclusive)

For example, you can fix a broken heart:

let spreadTheLove = ["<3", "💔", "love", "heart"];

spreadTheLove.fill("❤️", 1, 2);

spreadTheLove
// => [ '<3', '❤️', 'love', 'heart']
Enter fullscreen mode Exit fullscreen mode

Or, just fill them all with luuuve:

spreadTheLove = ["<3", "💔", "love", "heart"];

spreadTheLove.fill("❤️");

spreadTheLove
// => [ '❤️', '❤️', '❤️', '❤️' ]
Enter fullscreen mode Exit fullscreen mode

One distinguishing feature of .fill() method is that it only takes a single, static value. If you want to insert multiple values into an array, look elsewhere.

Recap

Again, here are the 9 essential array methods that mutate the original array:

.push() // => adds a new item as the last item of the array
.pop() // => removes the last item of the array

.unshift() // => adds a new item as the first item of the array
.shift() // => removes the first item of the array

.reverse() // => reverses the order of the array

.splice() // => removes/replaces item(s) in the array

.sort() // => re-orders the items in the array based on their Unicode code points

.copyWithin() // => copies one part of the array and put it on another part of the same array

.fill() // => changes some or all items in the array into the value being passed in
Enter fullscreen mode Exit fullscreen mode

By remembering these 9 methods I was able to expedite my coding process; it has also alleviated my worry of accidentally copying or mutating the array. Hope you find it useful, too!

Oldest comments (4)

Collapse
 
k_penguin_sato profile image
K-Sato

Greate Post!!

Collapse
 
jamiemccarville profile image
Jamie McCarville 🇨🇦

Great post Annie!

I just have one suggestion that could make the .sort() explanation a little clearer. You said just before the last code snippet:

"If you really want to sort the numbers in an order that makes human sense, you can pass in a compare function as an argument in the .sort() method:"

You then show the compare function in the snippet. I think it would be great to have a short explanation of how the compare function works, what it does to sort the numbers. I read the article that you linked to and it has a really great three bullet explanation. I think adding something similar just after the code snippet would help to make it just a little bit more clear.

Collapse
 
liaowow profile image
Annie Liao

This is spot-on, Jamie. Just added the explanation. Thanks so much for helping me perfect this post!

Collapse
 
liaowow profile image
Annie Liao

Thanks for reading, Ru! Given the mutable nature of JavaScript objects, I haven't found a similar pattern when it comes to built-in methods. Even a commonly-used method Object.assign() only creates a shallow copy of an object -- technically it does mutate, even though on the surface, it doesn't seem so.

Here's an article that explains further. Hope it helps! alistapart.com/article/why-mutatio...