Skip to main content

Ordering a Collection

Sorting means putting the elements of a collection in a certain order.

Some types have a natural order. Numbers are ordered by value: 1 comes before 2. Strings are ordered alphabetically: "four" comes before "one". For everything else, or when you want a different order, you give a custom order: a compare function that looks at two elements and says which one comes first.

Arrays have one sorting method, sort(), and two things about it are easy to miss:

  • sort() changes the array in place and returns that same array, so the original order is gone. To keep it, sort a copy: [...words].sort().
  • With no compare function, sort() compares the string form of the values. That is fine for words, but [10, 9, 2] comes out as [ 10, 2, 9 ], because the text "10" comes before "2". For numbers, always pass a compare function.

A compare function gets two elements, a and b, and returns a number: negative when a comes first, positive when b comes first, and 0 when they are equal. a - b does that for numbers and a.localeCompare(b) does it for strings.

sort() is stable: elements that the compare function calls equal keep their original order.

Newer versions of Node.js also have toSorted() and toReversed(), which return a new array and leave the original alone. The examples below copy the array by hand with [...words], which works everywhere.

The examples below use the words one, two, three, four.


Natural Order

Natural order is the order a type has on its own: numbers by value, strings letter by letter.

[...words].sort() gives a new array in alphabetical order. For descending order there is no separate method: sort first, then turn the array around with reverse(). For numbers, the compare function (a, b) => b - a does it in one step.

Your own object types have no natural order. You give them one by writing a compare function that returns a negative number, zero or a positive number, the same rule as any other compare function. You can then pass it to sort() or use it directly to compare two values.

Sorted copy

[...words].sort() sorts a copy, so words itself is printed unchanged afterwards. The last two lines show why numbers need a compare function: without one, [10, 9, 2] becomes [ 10, 2, 9 ]; with (a, b) => a - b it becomes [ 2, 9, 10 ].

Prompt
Write a TypeScript program that takes the words one, two, three, four, sorts a copy of them in natural (alphabetical) order with [...words].sort(), and prints the sorted copy and then the unchanged original. Then print [10, 9, 2] sorted with no compare function and sorted with (a, b) => a - b, to show why numbers need one.
Output: TypeScript file and captured output
Sorted copy

Sorted descending

The copy is sorted, then reverse() flips it, so the words go from "two" down to "four". The numbers show the shorter way for values you can subtract: (a, b) => b - a sorts [10, 9, 2] straight into [ 10, 9, 2 ].

Prompt
Write a TypeScript program that takes the words one, two, three, four, sorts a copy of them and turns it around with reverse(), so the words are in descending alphabetical order, and prints the result. Then print [10, 9, 2] sorted with the compare function (a, b) => b - a.
Output: TypeScript file and captured output
Sorted descending

A type with its own order

Version has a major and a minor number. compareVersion() compares major first; only when that result is 0 does minor decide, because || takes the first result that is not 0. Checking compareVersion(...) > 0 answers "is the first version greater?".

Prompt
Write a TypeScript program with a Version type that has major and minor numbers, and a compareVersion function that returns a negative number, zero or a positive number, comparing major first and then minor with ||. Print whether version 1.2 is greater than 1.3, then whether 2.0 is greater than 1.5, using compareVersion(...) > 0.
Output: TypeScript file and captured output
A type with its own order

Custom Orders

A custom order is any order you describe with a compare function.

sort() takes that function as its only argument. Because sort() is stable, elements the function calls equal stay in their original order, which is what you want when sorting words by length.

A common wish is to sort by one property of each element. There is no special method for that: write a compare function that compares that property, with - for numbers or localeCompare() for strings.

For descending order, swap a and b in the comparison.

To sort by one thing and then by another, join two comparisons with ||. It takes the first result that is not 0, so when the first comparison calls the elements equal, the second one breaks the tie.

Sort with a compare function

The compare function returns a.length - b.length. A negative result means a is shorter and comes first, so the words end up from shortest to longest: [ 'c', 'bb', 'aaa' ].

Prompt
Write a TypeScript program that takes the words aaa, bb, c and sorts a copy of them with the compare function (a, b) => a.length - b.length, so shorter words come first. Print the result.
Output: TypeScript file and captured output
Sort with a compare function

Sort by length

The words are compared with a.length - b.length. "one" and "two" are both 3 letters long; because sort() is stable, "one" stays before "two".

Prompt
Write a TypeScript program that takes the words one, two, three, four and sorts a copy of them by length with (a, b) => a.length - b.length. Print the result and let the stable sort keep words of the same length in their original order.
Output: TypeScript file and captured output
Sort by length

Sort by last letter, descending

a[a.length - 1] is the last letter of a word. The letters of b and a are compared in swapped order, so the word with the largest last letter comes first: "four" (r), then "two" (o), then the two words ending in "e" in their original order.

Prompt
Write a TypeScript program that takes the words one, two, three, four and sorts a copy of them by their last letter in descending order, by comparing the last letter of b with the last letter of a using localeCompare. Print the result.
Output: TypeScript file and captured output
Sort by last letter, descending

Sort by length, then alphabetically

|| compares the lengths first. When two words are equally long, it falls back to localeCompare(), which orders them alphabetically. The result is printed with join(' '), on one line: a b c aa bb aaa ccc.

Prompt
Write a TypeScript program that takes the words aaa, bb, c, b, a, aa, ccc and sorts a copy of them first by length and then alphabetically, using one compare function that joins a.length - b.length and a.localeCompare(b) with ||. Print the result on one line with join(' ').
Output: TypeScript file and captured output
Sort by length, then alphabetically

Check Sorted Order

Sometimes you only want to know whether an array is already sorted, without sorting it.

There is no built-in check, but every() writes one in a single line: every element must come after the element before it. The small isSorted() helper below takes a compare function, so the same helper can check ascending order, descending order, or the order of one property. For descending order you pass a compare function with a and b swapped.

Ascending and descending check

isSorted() confirms that 1, 2, 3, 4 are ascending. The same helper with (a, b) => b - a confirms that 4, 3, 2, 1 are descending. The element at index 0 is always accepted, because it has no element before it.

Prompt
Write a TypeScript program with a generic isSorted helper that uses every() and a compare function to check that each element comes after the one before it. Print whether 1, 2, 3, 4 are ascending, and whether 4, 3, 2, 1 are descending by passing a compare function with a and b swapped.
Output: TypeScript file and captured output
Ascending and descending check

Check order by a field

The users are Alice (24), Bob (31) and Charlie (29). The compare function looks only at age. Bob is older than Charlie, so the array is not sorted by age and the answer is false.

Prompt
Write a TypeScript program with a User type (name and age) and an array holding Alice 24, Bob 31 and Charlie 29. Use a generic isSorted helper with a compare function that compares only the ages, and print whether the users are sorted by age.
Output: TypeScript file and captured output
Check order by a field

Reverse Order

Reversing puts the last element first and the first element last.

reverse() reverses an array in place, just like sort() does. To get a reversed copy instead, reverse a copy: [...words].reverse(). The original keeps its order.

Reversed copy

The copy is reversed and printed, then the original is printed to show it is unchanged.

Prompt
Write a TypeScript program that takes the words one, two, three, four and reverses a copy of them with [...words].reverse(), so the original keeps its order. Print the reversed copy, then print the original.
Output: TypeScript file and captured output
Reversed copy

Random Order

Shuffling puts the elements in a random order.

There is no shuffle method. The usual way is a small loop that walks the array from the last position down and swaps each element with an element at an earlier position. The loop changes the array it is given, so shuffle a copy when you still need the original.

Math.random() would give a different order on every run. This example therefore seeds its own generator: two lines of arithmetic that turn a seed into a stream of numbers from 0 up to 1. The same seed always gives the same order, which is useful for tests and for examples like this one.

Shuffled copy

A generator with seed 9 shuffles a copy of the words and prints [ 'two', 'four', 'three', 'one' ]. Running the program again prints the same order, because the generator starts from the same seed.

Prompt
Write a TypeScript program with a small seeded random generator, so the output is the same on every run. Use it to shuffle a copy of the words one, two, three, four with a loop that walks from the last position down and swaps each element with an element at an earlier position, and print the shuffled copy.
Output: TypeScript file and captured output
Shuffled copy