The Array.prototype.sort() method is one of the most powerful yet frequently misunderstood tools in the JavaScript language. It is used to arrange the elements of an array in place and returns the reference to the same array, now sorted. Because it mutates the original data and relies on a specific default behavior, understanding its mechanics is essential for predictable programming.
1. The Default Behavior (The Trap)
By default, sort() converts elements into strings and compares their sequences of UTF-16 code unit values. This leads to the “Lexicographical Sort” problem, where numbers are not sorted by their numeric value but by their character value.
const scores = [1, 10, 2, 21];
scores.sort();
console.log(scores);
// Output: [1, 10, 2, 21]
// (Because "10" comes before "2" in string comparison)
2. The Compare Function
To sort correctly (numerically or by custom logic), you must provide a compare function. This function defines the sort order based on its return value:
- Negative Value (< 0): a is placed before b.
- Positive Value (> 0): b is placed before a.
- Zero (0): The relative order of a and b remains unchanged (though this depends on the stability of the engine).
A. Numeric Sorting
For numbers, the most concise way to sort is using subtraction:
const numbers = [40, 100, 1, 5, 25];
// Ascending Order
numbers.sort((a, b) => a - b); // [1, 5, 25, 40, 100]
// Descending Order
numbers.sort((a, b) => b - a); // [100, 40, 25, 5, 1]
B. String Sorting (Locale-Aware)
For strings with accents or special characters (like “ä” or “é”), standard comparison might fail. The localeCompare() method is the best practice here.
const items = ['réservé', 'premier', 'cliché', 'commun'];
items.sort((a, b) => a.localeCompare(b));
3. Sorting Objects
When dealing with complex data types, you access the specific property you wish to sort by within the compare function.
const users = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
{ name: "Charlie", age: 35 }
];
// Sort by age
users.sort((a, b) => a.age - b.age);
4. Stability and Algorithm Complexity
A stable sort preserves the original relative order of elements that have equal sort keys. Since ES2019, the JavaScript specification requires sort() to be stable.
- Complexity: Most modern engines (like V8 in Chrome/Node.js) use the Timsort algorithm (a hybrid of Merge Sort and Insertion Sort).
- Performance: The average time complexity is $O(n \log n)$.
5. Advanced: Sorting with localeCompare
For strings with special characters (accented letters, different alphabets), the standard < and > operators might fail. Use localeCompare() for robust, language-sensitive sorting.
const cities = ["Zürich", "Amsterdam", "Århus", "Berlin"];
// Standard sort would put "Ã…rhus" at the end
cities.sort((a, b) => a.localeCompare(b, 'de'));
// ["Amsterdam", "Århus", "Berlin", "Zürich"]
6. Advanced Pattern: Sorting with Map (The “Schwartzian Transform”)
If the compare function involves expensive operations (like fetching a value from a nested object or a calculated property), sorting large arrays can become slow because the compare function is called $O(n \log n)$ times.
To optimize, you can map the array to a temporary array containing the “sort keys,” sort the temporary array, and then map back to the original values.
// 1. Create temporary array with calculated sort keys
const mapped = list.map((el, i) => ({ index: i, value: expensiveTransform(el) }));
// 2. Sort the temporary array
mapped.sort((a, b) => a.value - b.value);
// 3. Map back to original elements using saved indices
const result = mapped.map(el => list[el.index]);
