jsguides

Array.prototype.toSorted()

toSorted(compareFn)

The toSorted() method returns a new array with the elements sorted. It’s the immutable counterpart to sort() — your original array stays untouched, which is essential for React and Redux as well as other scenarios where immutability matters.

Syntax

arr.toSorted(compareFn)

Parameters

ParameterTypeDescription
compareFnfunctionOptional function that defines sort order

The compare function receives two arguments:

  • a: First element for comparison
  • b: Second element for comparison

Return negative if a < b, positive if a > b, zero if equal.

Examples

These examples start with the default numeric sort and build through descending order, then string and object sorting, followed by chaining. Each block introduces one sorting behaviour that builds on the previous one.

Basic usage (numbers)

const arr = [3, 1, 2];
const sorted = arr.toSorted();
console.log(arr);    // [3, 1, 2] — original unchanged
console.log(sorted); // [1, 2, 3]

The default sort is ascending, but reversing the order requires a compare function. Passing (a, b) => b - a produces a descending sort — a pattern that works reliably for numbers but not for strings, where subtraction returns NaN.

Descending order

const nums = [5, 2, 8, 1, 9];
const descending = nums.toSorted((a, b) => b - a);
console.log(descending); // [9, 8, 5, 2, 1]

Numeric sorts are straightforward with subtraction, but strings need a different approach. The default lexicographic sort works for simple string arrays, but case sensitivity can lead to unexpected results when you mix capital and lowercase letters. Using localeCompare gives you more control over the order.

Sorting strings

const words = ['banana', 'apple', 'cherry', 'date'];
const alphabetical = words.toSorted();
console.log(alphabetical); // ['apple', 'banana', 'cherry', 'date']

// Case-insensitive sorting
const mixed = ['Apple', 'banana', 'Cherry', 'date'];
const caseInsensitive = mixed.toSorted((a, b) => 
  a.localeCompare(b)
);
console.log(caseInsensitive); // ['Apple', 'banana', 'Cherry', 'date']

Case-insensitive string sorting is one common use; sorting arrays of objects by a specific property is another. The compare function can access any key on the elements, making toSorted() a good fit for data tables and API response shaping.

Sorting objects by property

const users = [
  { name: 'Alice', age: 30 },
  { name: 'Bob', age: 25 },
  { name: 'Carol', age: 35 }
];

const byAge = users.toSorted((a, b) => a.age - b.age);
console.log(byAge.map(u => u.name)); // ['Bob', 'Alice', 'Carol']

const byName = users.toSorted((a, b) => a.name.localeCompare(b.name));
console.log(byName.map(u => u.name)); // ['Alice', 'Bob', 'Carol']

Sorting by one property is common, but toSorted() also works in method chains. Because it returns a new array, you can follow it with map(), filter(), or another toSorted() call — each step produces a fresh array without side effects on the original data.

Chaining with other methods

const data = [4, 2, 5, 1, 3];

const result = data
  .filter(n => n > 2)
  .toSorted()
  .map(n => n * 2);

console.log(result); // [6, 8, 10]

Chaining works well with clean data, but real arrays often contain null or undefined. The default sort places these values inconsistently across engines. A custom compare function gives you control over where they end up, which is essential for predictable output in production code.

Sorting with nulls and undefined

const mixed = [3, null, 1, undefined, 2];

// Put nulls/undefined at the end
const sorted = mixed.toSorted((a, b) => {
  if (a == null) return 1;
  if (b == null) return -1;
  return a - b;
});
console.log(sorted); // [1, 2, 3, null, undefined]

Handling nulls with a custom comparator works, but the most common mistake with toSorted() is forgetting that the default sort compares elements as strings. This catches many developers off guard when they first sort an array of numbers and get unexpected results.

Common mistakes

The default sort converts elements to strings:

[10, 2, 21].sort();        // [10, 2, 21] — wrong!
[10, 2, 21].toSorted();   // [10, 2, 21] — still wrong without compareFn!
[10, 2, 21].toSorted((a, b) => a - b); // [2, 10, 21] — correct

Without a compareFn, elements are converted to strings and sorted lexicographically. The string "10" sorts before "2" because "1" < "2". Always pass a numeric comparator for numeric arrays.

How it works

toSorted() creates a new array of the same length, copies all elements into it, then applies the same sorting algorithm as sort() — typically TimSort (a hybrid merge/insertion sort) in modern engines. The sort is guaranteed to be stable in ES2019+: elements that compare as equal keep their original relative order.

Stability matters when sorting by one field and then by another. A stable sort lets you chain sorts safely:

const records = [
  { name: 'Alice', dept: 'eng' },
  { name: 'Bob', dept: 'eng' },
  { name: 'Carol', dept: 'hr' }
];

// Sort by dept first, then by name — stable sort preserves dept groups
const sorted = records
  .toSorted((a, b) => a.name.localeCompare(b.name))
  .toSorted((a, b) => a.dept.localeCompare(b.dept));

toSorted() vs sort()

sort() mutates the array and returns it. toSorted() returns a new array and leaves the original intact. In React and Redux patterns, state arrays must not be mutated directly, making toSorted() the correct choice. The two methods are otherwise identical in their sorting behaviour and compare function signature.

// Mutating — wrong for state
const newState = state.items.sort((a, b) => a.id - b.id);

// Non-mutating — correct for state
const newState = state.items.toSorted((a, b) => a.id - b.id);

toSorted() on sparse arrays

toSorted() converts sparse array holes to undefined in the result. This is consistent with sort(). If your array may have holes and you need to preserve the sparse structure, use [...arr].sort() and then reconstruct, or filter out undefined values before sorting.

Browser support

toSorted() was introduced in ES2023 and is available in Chrome 110+, Firefox 115+, Safari 16+, and Node.js 20+. For projects that must support older environments, use [...arr].sort(compareFn) as a direct polyfill — the spread creates a shallow copy, and sort() then mutates only the copy, leaving the original intact.

// ES2023 native
const sorted = arr.toSorted((a, b) => a - b);

// Equivalent for older targets
const sorted = [...arr].sort((a, b) => a - b);

See Also