What sort() does
Every TypeScript array has a sort method. It rearranges the elements in place, so the array you call it on changes, and it also returns that same array. TypeScript compiles to JavaScript, so sort behaves exactly as it does in JavaScript; the types only describe the array, they don't change how the sort works.
Called with no arguments, sort converts every element to a string and compares the strings character by character, in ascending order. That is fine for words:
let teams: string[] = ["Real Madrid", "Manchester Utd", "Bayern Munich", "Juventus"];
teams.sort();
console.log(teams);
[ 'Bayern Munich', 'Juventus', 'Manchester Utd', 'Real Madrid' ]
Why numbers need a compare function
With numbers, the same default is a trap. The values are turned into strings first, and the string "23" sorts before "3" because the character 2 comes before 3:
let values: number[] = [3, 23, 12];
values.sort();
console.log(values);
[ 12, 23, 3 ]
The compiler doesn't warn about this, because the call is valid. The fix is to pass a compare function. It receives two elements, a and b, and returns a number:
- a negative number means
agoes beforeb; - a positive number means
bgoes beforea; - zero means the two are equal for this sort, so their order is kept.
For numbers, a - b gives exactly those signs for ascending order, and b - a gives descending order:
let values: number[] = [3, 23, 12];
values.sort((a, b) => a - b);
console.log(values);
values.sort((a, b) => b - a);
console.log(values);
[ 3, 12, 23 ]
[ 23, 12, 3 ]
The arrow function (a, b) => a - b is shorthand for function (a, b) { return a - b; }. When the body is a single expression, you can drop the braces and the return.
Sorting arrays of arrays
A compare function can look at any part of the elements. To sort pairs by the second value ascending and, on a tie, by the first value descending, check the tie first and return the tie-breaker; otherwise return the main comparison:
let pairs: number[][] = [[1, 3], [10, 1], [12, 1], [7, 5]];
pairs.sort((a, b) => {
if (a[1] == b[1]) {
return b[0] - a[0];
}
return a[1] - b[1];
});
console.log(JSON.stringify(pairs));
[[12,1],[10,1],[1,3],[7,5]]
Sorting strings without caring about case
The default order compares character codes, and every uppercase letter has a smaller code than every lowercase one. So "john" would land after "Mircea":
let people: string[] = ["Mircea", "john", "Andy"];
people.sort();
console.log(people);
[ 'Andy', 'Mircea', 'john' ]
The string method localeCompare returns a negative number, zero or a positive number, just like a compare function must. Lowercasing both sides first makes the comparison ignore case. Subtracting strings, as in a - b, doesn't work: the compiler rejects the subtraction, and the result at run time would be NaN.
The lesson's exercise, step by step
The exercise has three small sorts.
- Numbers, descending. Declare
numbersas anumber[]holding 2, 1, 3, 10, then sort it with(a, b) => b - a. Without the compare function, 10 would come before 2. - Pairs, by two keys. Declare
coordinatesas anumber[][]with the pairs[3, 2],[2, 3]and[3, 5]. Sort by the first value ascending; when two first values are equal, sort by the second value descending. - Names, ignoring case. Declare
namesas astring[]with"Mircea","john"and"Andy", and sort withtoLowerCase()andlocaleCompare.
This is the full solution:
let numbers: number[] = [2, 1, 3, 10];
numbers.sort((a, b) => b - a);
let coordinates: number[][] = [
[3, 2],
[2, 3],
[3, 5],
];
coordinates.sort((a, b) => {
if (a[0] == b[0]) {
return b[1] - a[1];
}
return a[0] - b[0];
});
let names: string[] = ["Mircea", "john", "Andy"];
names.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
After these lines, the three arrays hold:
numbers [ 10, 3, 2, 1 ]
coordinates [[2,3],[3,5],[3,2]]
names [ 'Andy', 'john', 'Mircea' ]
In the pairs, [3, 5] comes before [3, 2] because the first values tie and 5 is larger than 2. The trailing comma after [3, 5] is allowed and makes adding another line easier.
Cost of sorting
Sorting n elements makes about n log n comparisons. If one comparison takes C time, sorting takes O(n * log n * C). Comparing two numbers is constant time, so sorting numbers is O(n log n). Comparing two strings takes time proportional to their length L, so sorting strings is O(n * log n * L).
The sort happens in place, so you can think of the extra space as small. In practice, JavaScript engines may use some working memory internally, which is why this is a rough rule rather than a promise.
Common mistakes
- Forgetting the compare function on numbers. The result looks sorted for single digits and goes wrong as soon as lengths differ.
- Expecting a new array.
sortchanges the original and returns it, solet sorted = values.sort(...)gives you a second name for the same array. To keep the original, copy it first:[...values].sort((a, b) => a - b). - Mixing up the direction.
a - bis ascending andb - ais descending. - Returning a boolean. A compare function like
(a, b) => a > breturnstrueorfalse, never a negative number, so it doesn't sort reliably.
Key takeaways
sort()sorts in place and, without a compare function, compares elements as strings.- For numbers, pass
(a, b) => a - bfor ascending order or(a, b) => b - afor descending order. - For several keys, return the tie-breaker when the main keys are equal.
- For case-insensitive text, lowercase both sides and use
localeCompare. - Sorting numbers costs
O(n log n).