Array.prototype.toSorted()

One of four "change by copy" methods added in ES2023. It fixes sort's mutation problem and nothing else — the string-comparison trap comes along unchanged.

Array methodES2023Live demo
Common call
items.toSorted((a, b) => a - b)
Returns
a new sorted array; the original is untouched
Replaces
[...items].sort(...)
Watch out
still sorts as STRINGS without a comparator
array.toSorted([compareFn])
→ Array

Demo

Live evaluation
Try:
Inputs
itemsnumber[]numbers, comma separated
Output
[10, 9, 1].toSorted()
[1, 10, 9]

Compare this with sort and you will see identical output — [10, 9, 1] still becomes [1, 10, 9], because toSorted inherits the default string comparator wholesale. The ONLY thing it changes is that the source array is untouched, which the demo cannot show. If you were hoping the newer method also fixed the number sorting, it does not: you still need (a, b) => a - b.

Parameters

NameTypeRequiredDescription
compareFnFunctionno (string comparison)Same contract as sort: return negative, zero or positive. Omitted, elements are compared as strings — exactly as sort does.

Return value

Array — A NEW sorted array. The original is left exactly as it was — the only difference from sort.

Common patterns

Sort React state safely
A new array means a new reference, which is what re-render logic needs.
setItems(items.toSorted((a, b) => a - b));
Sort a prop without side effects
The parent's array is untouched.
const shown = props.rows.toSorted(byName);
The pre-2023 equivalent
Copy, then sort the copy.
const sorted = [...items].sort(cmp);

Examples

1. String trap survives
[10, 9, 1].toSorted()
Returns
[1, 10, 9]
2. With a comparator
[10, 9, 1].toSorted((a, b) => a - b)
Returns
[1, 9, 10]
3. Original untouched
const a = [3, 1]; a.toSorted(); a
Returns
[3, 1]
4. sort would mutate
const a = [3, 1]; a.sort(); a
Returns
[1, 3]
5. A different array
const a = [1]; a.toSorted() === a
Returns
false
6. Empty array
[].toSorted()
Returns
[]

Pitfalls

1. It does not fix the string-sorting trap
The most misleading thing about the newer method. It solves mutation, not comparison — numbers are still converted to strings without a comparator, so [10, 9, 1] still comes out as [1, 10, 9].
Still string order
[10, 9, 1].toSorted()
[1, 10, 9]
Comparator still needed
[10, 9, 1].toSorted((a, b) => a - b)
[1, 9, 10]
2. The copy is shallow
A new outer array holding the SAME objects. Sorting does not clone the elements, so mutating one through the sorted array changes it in the original too.
Shared objects
const a = [{n: 1}];
const b = a.toSorted();
b[0].n = 9;
a[0].n
9
Deep copy
const b = structuredClone(a).toSorted(cmp);
independent
3. ES2023 and newer only
Node 20+, and reasonably recent browsers. Older targets need the copy-then-sort idiom, which behaves identically.
Missing method
items.toSorted(cmp)
TypeError: items.toSorted is not a function
Copy first
[...items].sort(cmp)
same result

When to use

Use it
  • Sorting state, props or any array you must not mutate
  • Chaining a sort into an expression without a separate copy step
  • Anywhere [...items].sort(...) appears today
Reach for something else
  • You own the array and want to sort in place → sort is cheaper
  • Runtimes older than ES2023 → [...items].sort()
  • You need the nested objects independent too → deep copy first

Notes

Complexity
O(n log n), plus O(n) for the copy
Return
A new array; the original is never modified
CPython impl
V8: Builtins-array-tosorted.tq
Memory
Allocates a full copy before sorting
Thread-safe
Single-threaded; the source is only read

FAQ

No — it has exactly the same default as sort, comparing elements as strings. The ES2023 methods addressed mutation, not comparison. Pass (a, b) => a - b for numbers, just as you always did.

[10, 9, 1].toSorted()               // [1, 10, 9]
[10, 9, 1].toSorted((a, b) => a - b) // [1, 9, 10]

History

ES2023
Added by the change-array-by-copy proposal, alongside toReversed, toSpliced and with.