Sorting Arrays without Mutating Them
Exploring the new way to sort arrays in JavaScript with toSorted
This summer, ECMAScript 2023 was released and with it came a new method for sorting arrays: toSorted
. It also came with toReversed
, toSpliced
, findLast
, and with
methods. The to___
methods are similar to the sort
, reverse
, and splice
methods, but they return a new array instead of mutating the original array.
I though it would be fun to compare the new toSorted
method with the old sort
method.
The sort
method
Previously, the sort
method was the go to for sorting array. Although it had a potential drawback: it mutated the original array.
const arr = [3, 2, 1]
arr.sort()
console.log(arr) // [1, 2, 3]
As you can see the original array was mutated. This can be a problem if you want to keep the original array around.
One solution for avoiding this was to create a copy of the array before sorting it.
const arr = [3, 2, 1]
const sortedArr = [...arr].sort()
console.log(arr) // [3, 2, 1]
console.log(sortedArr) // [1, 2, 3]
By using the spread operator, we create a new array with the same values as the original array. This new array can be sorted without mutating the original array.
The toSorted
method
Now, with the new toSorted
method, we can sort an array without mutating the original array.
const arr = [3, 2, 1]
const sortedArr = arr.toSorted()
console.log(arr) // [3, 2, 1]
console.log(sortedArr) // [1, 2, 3]
Comparing the two methods
I ran some tests to compare the two methods. I created an array of 100,000 random string (using nanoid
) and sorted it using both methods. Since this was mostly about curiousity, I only ran the test a few times. This is the code I used:
// Define nanoid (copied from the nanoid project) https://github.com/ai/nanoid
let nanoid = (t = 21) =>
crypto
.getRandomValues(new Uint8Array(t))
.reduce(
(t, e) =>
(t += (e &= 63) < 36 ? e.toString(36) : e < 62 ? (e - 26).toString(36).toUpperCase() : e > 62 ? "-" : "_"),
"",
)
// create array of 100k
const sampleArray = Array.from({ length: 100000 }, () => nanoid())
// measure spread operator + sort (copy + sort)
const start = performance.now()
;[...sampleArray].sort()
const end = performance.now()
console.log(`Time to sort ${arr.length} nanoid strings with [...arr].sort(): ${end - start} ms`)
// measure new toSorted method
const startBeta = performance.now()
sampleArray.toSorted()
const endBeta = performance.now()
console.log(`Time to sort ${arr.length} nanoid strings with arr.toSorted(): ${endBeta - startBeta} ms`)
The results were as follows:
// Time to sort 10000 nanoid strings with [...arr].sort(): 53.5 ms
// Time to sort 10000 nanoid strings with arr.toSorted(): 29.099999994039536 ms
It might take some time to adjust, but I'll probably be using the new toSorted
method from now on. 😇