Start learning free

LESSON 199 OF 348 · SORTING

Sorting in TypeScript: sort() and Compare Functions

Lesson Read the full explanation

In JavaScript, we can sort the elements of an array easily with a built-in method called the sort() function.

The default sort order is ascending, built upon converting the elements into strings, then comparing them lexicographically.


Array of strings:

When we use the sort() method, elements will be sorted in ascending order (A to Z) by default:

let teams = ['Real Madrid', 'Manchester Utd', 'Bayern Munich', 'Juventus'];

teams.sort(); 

// ['Bayern Munich', 'Juventus', 'Manchester Utd', 'Real Madrid']

Arrays of numbers:

Sorting numbers is unfortunately not that simple. If we apply the sort() method directly to a numbers array, we will see an unexpected result:

let numbers = [3, 23, 12];

numbers.sort(); // --> 12, 23, 3

By default, the sort() function sorts values as strings.

This works well for strings ("Apple" comes before "Banana").

However, if numbers are sorted as strings, "3" is bigger than "23", because "3" is bigger than "2".

Because of this, the sort() method will produce incorrect results when sorting numbers.


Solution: The Compare Function

Luckily, we can support the sort() method with a basic comparison function which will do the trick:

function(a, b) {return a - b}

When the sort() function compares two values, it sends the values to the compare function, and sorts the values according to the returned (negative, zero, positive) value:

  • If the result is negative a is sorted before b.
  • If the result is positive b is sorted before a.
  • If the result is 0 no changes are done with the sort order of the two values.

All we need to is using the compare function inside the sort() method:

let numbers = [3, 23, 12];

numbers.sort(function(a, b) {return a - b}); // --> 3, 12, 23

JavaScript gives us a way to make this code shorter using arrow functions:

numbers.sort(function(a, b) {return a - b});

// can be rewritten as:
numbers.sort((a, b) => {return a - b});

// and even as:
numbers.sort((a, b) => a - b);

If we want to sort the numbers in descending order, this time we need to subtract the second parameter (b) from the first one (a):

let numbers = [3, 23, 12];

numbers.sort((a, b) => b - a); // --> 23, 12, 3

Arrays of pairs:

Let's see another scenario when the comparison function is very useful: sorting an array, where each element is another array consisting of two numbers, ascending by the second values and in case of equality, descending by the first values:

let pairs = [[1, 3], [10, 1], [12, 1], [7, 5]];

//sort the array

// Desired outcome: pairs = [[12, 1], [10, 1], [1, 3], [7, 5]]

Here is how we would do that:

let pairs = [[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];
});

// pairs = [[12, 1], [10, 1], [1, 3], [7, 5]]

Time Complexity:

Sorting an array via sort() takes O(n * log n * C) time, where C is the time taken to compare two elements from the array.

It takes O(1) (constant time) to compare two numbers, therefore sorting an array of numbers takes O(n log n).

However, it takes O(L) to compare two strings, therefore sorting an array of strings takes O(n * log n * L), where L is the average length of the strings.


Space Complexity:

The sort() method sorts the elements of an array in place (transforms the array directly, using no auxiliary data structure), so the extra space used for this is O(1).


Assignment
Follow the Coding Tutorial and let's sort some arrays.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 6
    Ctrl + Enter to check

    TYPESCRIPT · SORTING

    Sorting in TypeScript, explained

    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 a goes before b;
    • a positive number means b goes before a;
    • 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.

    1. Numbers, descending. Declare numbers as a number[] holding 2, 1, 3, 10, then sort it with (a, b) => b - a. Without the compare function, 10 would come before 2.
    2. Pairs, by two keys. Declare coordinates as a number[][] 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.
    3. Names, ignoring case. Declare names as a string[] with "Mircea", "john" and "Andy", and sort with toLowerCase() and localeCompare.

    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. sort changes the original and returns it, so let 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 - b is ascending and b - a is descending.
    • Returning a boolean. A compare function like (a, b) => a > b returns true or false, 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 - b for ascending order or (a, b) => b - a for 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).
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free