Skip to content
JavaScript js arrays 4 min read

Sorting Arrays

Sorting is one of the most common array operations, and also one of the easiest to get subtly wrong. JavaScript’s Array.prototype.sort() reorders elements in place and, by default, compares them as strings — which produces surprising results for numbers. This page shows how to sort correctly with a compare function, how to sort objects, why stability matters, and how to sort without mutating the original using toSorted.

sort mutates in place

sort() rearranges the elements of the array it is called on and returns a reference to that same array. There is no new array — the original is changed.

const fruits = ["cherry", "apple", "banana"];
const result = fruits.sort();

console.log(result);          // sorted
console.log(fruits);          // also sorted — same array
console.log(result === fruits); // true

Output:

[ 'apple', 'banana', 'cherry' ]
[ 'apple', 'banana', 'cherry' ]
true

Warning: Because sort() mutates, calling it on a shared array (props, state, a cached list) can cause hard-to-trace bugs. Copy first (see toSorted below) when you need to preserve the original.

The default lexicographic pitfall

With no arguments, sort() converts every element to a string and orders them by UTF-16 code units. For strings of plain ASCII letters that often looks fine, but for numbers it is almost never what you want.

const nums = [10, 1, 21, 2, 100, 3];
nums.sort();
console.log(nums);

Output:

[ 1, 10, 100, 2, 21, 3 ]

"10" sorts before "2" because the character "1" comes before "2". The fix is to supply a compare function.

Numeric sorting with a compare function

A compare function takes two elements, a and b, and returns a number that tells sort their relative order:

Return valueMeaning
negative (< 0)a comes before b
0leave order unchanged (relative to each other)
positive (> 0)a comes after b

For numbers, subtracting does exactly this:

const nums = [10, 1, 21, 2, 100, 3];

nums.sort((a, b) => a - b);   // ascending
console.log(nums);

nums.sort((a, b) => b - a);   // descending
console.log(nums);

Output:

[ 1, 2, 3, 10, 21, 100 ]
[ 100, 21, 10, 3, 2, 1 ]

Sorting strings correctly

Default string sorting is case-sensitive (uppercase letters sort before lowercase) and ignores locale rules like accents. For human-readable ordering, use localeCompare:

const names = ["Zoe", "ailsa", "Ben", "Évariste"];

names.sort();
console.log(names);

names.sort((a, b) => a.localeCompare(b));
console.log(names);

Output:

[ 'Ben', 'Zoe', 'Évariste', 'ailsa' ]
[ 'ailsa', 'Ben', 'Évariste', 'Zoe' ]

localeCompare returns a negative, zero, or positive number just like a compare function expects, so it drops straight in.

Sorting arrays of objects

Compare functions shine when sorting objects by a property. Reach into each object and compare the relevant field.

const users = [
  { name: "Ada", age: 36 },
  { name: "Linus", age: 28 },
  { name: "Grace", age: 36 },
];

// By age ascending
users.sort((a, b) => a.age - b.age);

// By name alphabetically
users.sort((a, b) => a.name.localeCompare(b.name));

console.log(users);

Output:

[
  { name: 'Ada', age: 36 },
  { name: 'Grace', age: 36 },
  { name: 'Linus', age: 28 }
]

For multi-key sorting, chain comparisons with || — the first non-zero result wins:

users.sort((a, b) => a.age - b.age || a.name.localeCompare(b.name));

This sorts by age first, then breaks ties alphabetically by name.

Stability

A sort is stable if elements that compare as equal keep their original relative order. Since ES2019, sort() is guaranteed stable in all conforming engines (Node and modern browsers). That guarantee is what makes multi-pass sorting reliable: sort by a secondary key first, then by the primary key, and equal primaries retain the secondary order.

const scores = [
  { team: "B", points: 10 },
  { team: "A", points: 10 },
  { team: "A", points: 20 },
];

scores.sort((a, b) => b.points - a.points);
console.log(scores.map((s) => `${s.team}:${s.points}`));

Output:

[ 'A:20', 'B:10', 'A:10' ]

The two 10-point teams stay in their input order (B before A) because the sort is stable.

toSorted — sorting without mutating

ES2023 added toSorted(), which returns a new sorted array and leaves the original untouched. It takes the same optional compare function as sort().

const nums = [3, 1, 2];

const sorted = nums.toSorted((a, b) => a - b);

console.log(sorted); // new array
console.log(nums);   // original unchanged

Output:

[ 1, 2, 3 ]
[ 3, 1, 2 ]

If you need broad support for older runtimes, copy first with the spread operator and sort the copy: const sorted = [...nums].sort((a, b) => a - b);.

MethodMutates original?ReturnsAvailable since
sort()YesThe same (sorted) arrayES1
toSorted()NoA new sorted arrayES2023

Best Practices

  • Always pass a compare function when sorting numbers — never rely on the default string sort.
  • Use toSorted() (or [...arr].sort()) when the source array must stay intact, especially with shared/state data.
  • Use a.localeCompare(b) for user-facing string ordering instead of the default < comparison.
  • Chain comparisons with || for multi-key sorting rather than nesting if statements.
  • Lean on guaranteed stability (ES2019+) for predictable tie-breaking and multi-pass sorts.
  • Keep compare functions pure and side-effect free, and ensure they return a number — returning a boolean is a common bug (true/false coerce to 1/0).
Last updated June 1, 2026
Was this helpful?