JavaScript — New, Immutable Array Methods

JavaScript arrays come with dozens of helpful methods. Chaining methods is also possible because some of the methods return an array instance, too. That means you can create your array pipelines and adjust the values to your needs by filtering, mapping, sorting, and so on.

But some of the array methods like sort or reverse mutate the array. Mutating the original array could cause side effects. For example, you may see correct results every second request because of a reverse operation.

This method shows you the newly introduced immutable JavaScript array methods.

JavaScript Series Overview

The Problem: Mutating Array Methods

Some JavaScript array methods mutate the original array. For example, the sort, reverse, and pop methods change the values within the original array:

const arr = [1, 4, 2, 3]

arr.sort()  
console.log(arr)  
// [1, 2, 3, 4]

arr.reverse()  
console.log(arr)  
// [4, 3, 2, 1]

arr.pop()  
console.log(arr)  
// [4, 3, 2]

Until now, you had to create an array copy with [...array] or arr.slide() first to not change the original array when using mutating methods. In the future, you don’t need to copy your array before working with it when using the new immutable array methods. Sweet!

Immutable Array Methods in JavaScript

JavaScript proposed new Array methods this year and runtimes including browsers already shipped them in new releases. We’re looking at the newly added immutable array methods.

Array#at(index)

The Array#at method returns the item at a given index. You can also use a negative index. If the provided index is negative, JavaScript accesses index + array.length. The at method returns undefined if the index is out of bounds or doesn’t match an item in the array:

const arr = [1, 4, 2, 3]  
const first = arr.at(0) // 1  
const last = arr.at(-1) // 3  
const prelast = arr.at(-2) // 2  
const unavailable = arr.at(10) // undefined  

Array#with(index, value)

The Array#with method replaces an array item at a given position with the provided value. You can’t add new items by using an index outside the bounds of the array. You can use negative values, JavaScript uses index + array.length then:

const arr = [1, 4, 2, 3]  
const newArr = arr.with(1, 10) // [1, 10, 2, 3]  
const newArr = arr.with(-2, 10) // [1, 4, 10, 3]

💥 const newArr = arr.with(4, 10) // RangeError: Invalid index : 4 -> you’re out of bounds

Array#toSorted(compareFn)

The Array#toSorted method is a copying, drop-in replacement for the sort method. It returns a new array with the items sorted in ascending order. You may also provide a compareFn defining the sort order:

const arr = [1, 4, 2, 3]  
const newArr = arr.toSorted() // [1, 2, 3, 4]  
const newArr = arr.toSorted((a, b) => b - a) // [4, 3, 2, 1]  

Array#toReversed()

The Array#toReversed method is a copying, drop-in replacement for the reverse method. It returns a new array having its items in reversed order:

const arr = [1, 4, 2, 3]  
const newArr = arr.toReversed() // [4, 3, 2, 1]  

Array#toSpliced(start, deleteCount, ...items)

The Array#toSpliced method is a copying, drop-in replacement for the splice method. It returns a new array with items replaced and/or removed at a given index:

const arr = [1, 4, 2, 3]  
const newArr = arr.toSpliced(2) // [1, 4]  
const newArr = arr.toSpliced(2, 1) // [1, 4, 3]  
const newArr = arr.toSpliced(2, 1, 10) // [1, 4, 10, 3]  
const newArr = arr.toSpliced(2, 1, 10, 11, 12) // [1, 4, 10, 11, 12, 3]  

Browser And Runtime Support

All modern browsers support the new methods. For example, you can check the caniuse.com for toSorted to detect whether browsers support the method.

Node.js v20 and later support all the mentioned here.

Converting Mutating Methods to Non-Mutating Alternatives

We mentioned the mutating sort, reverse, and pop methods in the beginning. JavaScript has more mutating methods. Here’s an overview of how you could replace the related methods in your code:

| Mutating Method | Non-Mutating Alternative | |-------------------|----------------------------| | pop() | slice(0, -1) or at(-1) | | push(v1, v2) | concat(v1, v2) | | reverse() | toReversed() | | shift() | slice(1) | | sort() | toSorted() | | splice() | toSpliced() | | unshift(v1, v2) | toSpliced(0, 0, v1, v2) |

As you noticed, the non-mutating replacements are possibly harder to read. For example, replacing pop with slice(0, -1) makes me stop to think about the action that happens. Replacing pop with at(-1) seems easier to read and understand.

Also, there’s nothing wrong with mutating arrays when used correctly. Keep using the pop method if you want to get and remove the last element from an array.

Use the @supercharge/arrays Package

I’m the maintainer of the @supercharge/arrays package providing a fluent Array class. The exported Arr class from @supercharge/arrays works like the native JavaScript Array. You’re wrapping an existing array, creating your call chain, and retrieving the final result as a native array using the toArray method.

All methods in @supercharge/arrays are immutable and don’t change the original array. Here’s an example using the sort and reverse methods to transform the wrapped values, but don’t mutate the source array:

import { Arr } from '@supercharge/arrays'

const arr = [1, 4, 2, 3]

const newArr = Arr.from(arr).sort().reverse().toArray()  
// arr: [1, 4, 2, 3]
// newArr: [4, 3, 2, 1]

Enjoy new JavaScript array methods!


Mentioned Resources

Explore the Library

Find interesting tutorials and solutions for your problems.