First steps in Typescript Array Sorting

210329-0407
Sorting an Array of elements
Typescript -like JavaScript does- offers a built in Array sorting method .sort(). The general syntax is:
array.sort( comparisonFunction );
A comparison Function is a function that specifies the sort order. If omitted, the array is sorted by default (lexicographically).
e.g.
var arr = new Array("orange", "mango", "banana", "sugar"); var sorted = arr.sort();
Returned string is :
banana,mango,orange,sugar
Omitting a Comparison function results in a lexicographically sorted array which means that the result we receive, e.g. sorting an array of numbers (using the default .sort() method), might not be what it should be expected.
e.g.
console.log("=========================...======================\n"); console.log([5,2,11,55,1,22].sort());
the result is:
=========================...====================== [ 1, 11, 2, 22, 5, 55 ]
However, using a proper comparison function solves it. The comparison function takes 2 parameters of type to be the same object of the array, (e.g. numbers), and returns a number 1, -1, or 0:
For ascending order, each time it returns:
- 1 if the 1st value is greater than the 2nd
- -1 if the 1st value is smaller than the 2nd, and
- 0 if they are equal
For descending order, each time it returns:
- -1 if the 2nd value is greater than the 1st
- 1 if the 1st value is greater than the 2nd, and
- 0 if they are equal
e.g. of a comparison function for sorting an array of numbers:
console.log([5,2,11,55,1,22].sort(compareArrayOfNumbersAsc)); function compareArrayOfNumbersAsc(n1: number, n2:number) :number { if (n1 > n2) { return 1; } if (n1 < n2) { return -1; } return 0; }
the result is:
[ 1, 2, 5, 11, 22, 55 ]
Of course, we can use an inline lambda anonymous function like the one below, but its better to take the next steps for better understanding.
console.log([5,2,11,55,1,22].sort((n1,n2) => n1 - n2));
Sorting an Array of Objects
Actually, we will use the .sort(…) array function we show before, but this time we have to provide a comparison function with the object properties we want to be compared.
Let say we have an array of employees’ data, like:
console.log([5,2,11,55,1,22].sort((n1,n2) => n1 - n2));
interface Emp { firstName: string; lastName: string; age: number; joinedDate: Date; } const EMPLOYEES: Emp[] = [ { firstName: 'John', lastName: 'Ripper', age: 27, joinedDate: 'November 16, 2020' }, { firstName: 'Ana', lastName: 'Karenina', age: 22, joinedDate: 'March 25, 2019' }, { firstName: 'Alan', lastName: 'Pikard', age: 33, joinedDate: 'April 11, 2018' } ];
Then we have to define the comparison function. The comparison function accepts 2 parameters of type of the same object of the array of objects, to be sorted. Then we can define which of the property/field of the object will be compared. In our case this the ‘firstName’ property.
Here we use the ascending order:
var compareByFirstName = function (emp1: Emp, emp2: Emp) { if (emp1.firstName > emp2.firstName) { return -1; } if (emp1.firstName < emp2.firstName) { return 1; } return 0; }
Of course, we can shorten our code using the if..else ternary operator:
var compareByFirstName = function (a: Emp, b: Emp) { return 0 - (a.firstName > b.firstName ? -1 : 1) ; }
Finally, we have to use the Array.sort() function but this time by using the comparison function as parameter, like that:
let sortedEmployees: Emp[] = []; sortedEmployees = employees.sort(compareByFirstName);
Alternatively, we can also use an anonymous (Lambda) statement/function directly in the Array.sort().
A Lambda statement is an anonymous function declaration that points to a block of code. When we pass a parameter(s), it is not mandatory to specify the data type of a parameter. In such a case the data type of the parameter is any. Typescript recognizes and infers the real type of the parameter(s).
In our case this will be like that: (a, b) => (a.propertyToSortBy < b.propertyToSortBy ? -1 : 1)
let sortedEmployees: Emp[] = []; sortedEmployees = employees.sort((a,b) => (a.firstName < b.firstName ? -1 : 1));
After all the above, we can also make a more general comparison function, accepting parameters such as a string parameter as a direction (e.g. ascending: ‘asc’, descending: ‘desc’), and another string parameter with the property/field name of the object to be compared (e.g. ‘firstName’, ‘age’, etc.).
function sort1(employees: Emp[], field: string, direction: string): Emp[] { let sortedEmployees: Emp[] = []; if (direction === '') { return employees; } else { if (direction === 'desc') { sortedEmployees = employees.sort((a,b) => (a[field as keyof Emp] < b[field as keyof Emp] ? 1 : -1)); } else { sortedEmployees = employees.sort((a,b) => (a[field as keyof Emp] < b[field as keyof Emp] ? -1 : 1)); } return sortedEmployees; } }
We can also restrict the values passed to direction parameter we can define our String Literal – Union Type, like that:
type SortDirection = 'asc' | 'desc' | '';
and then we can call the sort function like that:
function sort1(employees: Emp[], field: string, direction: SortDirection): Emp[] { . . .
Now, if we are trying to pass a wrong value, e.g. “esc”, the Typescript Compiler warns us:

This is the whole code so far:
// ********************************************************************** // This is a DEMO for SORTING in Typescript // ********************************************************************** let message: string = ' **** DEMO ==> SORTING in TYPESCRIPT ****'; console.log(message); interface Emp { firstName: string; lastName: string; age: number; joinedDate: Date; } const EMPLOYEES: Emp[] = [ { firstName: 'John', lastName: 'Ripper', age: 27, joinedDate: new Date('November 16, 2020') }, { firstName: 'Ana', lastName: 'Karenina', age: 22, joinedDate: new Date('March 25, 2019') }, { firstName: 'Alan', lastName: 'Pikard', age: 33, joinedDate: new Date('April 11, 2018') } ]; type SortDirection = 'asc' | 'desc' | ''; let direction: SortDirection = "asc"; console.log(sort1(EMPLOYEES,"age",direction)); function sort1(employees: Emp[], field: string, direction: SortDirection): Emp[] { let sortedEmployees: Emp[] = []; if (direction === '') { return employees; } else { if (direction === 'desc') { sortedEmployees = employees.sort((a,b) => (a[field as keyof Emp] < b[field as keyof Emp] ? 1 : -1)); } else { sortedEmployees = employees.sort((a,b) => (a[field as keyof Emp] < b[field as keyof Emp] ? -1 : 1)); } return sortedEmployees; } }
Immutability of an array in Sorting
Pay attention that arrays like objects are used and passed by reference and NOT by value. This means that they are mutable. Array operations like .sort() (or the opposite operation: .reverse()) are a good examples for demonstrating it.
Consider we have an array of numbers, e.g. myArray1 and then we sort it, returning the results to another array myArray2. Look in the console what happens when we show again the 1st Array:
const myArray1: number[] = [3,5,11,4,2,22]; console.log("1st Array ==> ", myArray1) const myArray2 = myArray1.sort((n1,n2) => n1 - n2); console.log("2nd Array ==> ", myArray2) console.log("1st Array again ==> ", myArray1)
The 1st Array has been changed and is now sorted, like the 2nd has been sorted:
1st Array ==> [ 3, 5, 11, 4, 2, 22 ] 2nd Array ==> [ 2, 3, 4, 5, 11, 22 ] 1st Array again ==> [ 2, 3, 4, 5, 11, 22 ]
A quick solution to preserve the 1st array order is to destruct (or spread) it using the rest … syntax. Doing so, we actually make a shallow copy of the 1st array, and thus, the mutation happens on the copy, instead of the original array:
const myArray1: number[] = [3,5,11,4,2,22]; console.log("1st Array ==> ", myArray1) const myArray2 = [...myArray1].sort((n1,n2) => n1 - n2); console.log("2nd Array ==> ", myArray2) console.log("1st Array again ==> ", myArray1)
This time the 1st array is preserved:
1st Array ==> [ 3, 5, 11, 4, 2, 22 ] 2nd Array ==> [ 2, 3, 4, 5, 11, 22 ] 1st Array again ==> [ 3, 5, 11, 4, 2, 22 ]
So, whenever you want to preserve an array, remember to use the above syntax.
And this is how we can use it in the sort1 function in the previous code example:
. . . let direction: SortDirection = "asc"; console.log(sort1(EMPLOYEES,"age",direction)); function sort1(employees: Emp[], field: string, direction: SortDirection): Emp[] { let sortedEmployees: Emp[] = []; if (direction === '') { return employees; } else { if (direction === 'desc') { sortedEmployees = [...employees].sort((a,b) => (a[field as keyof Emp] < b[field as keyof Emp] ? 1 : -1)); } else { sortedEmployees = [...employees].sort((a,b) => (a[field as keyof Emp] < b[field as keyof Emp] ? -1 : 1)); } return sortedEmployees; } }
And the result is:
[ { firstName: 'Ana', lastName: 'Karenina', age: 22, joinedDate: 2019-03-24T22:00:00.000Z }, { firstName: 'John', lastName: 'Ripper', age: 27, joinedDate: 2020-11-15T22:00:00.000Z }, { firstName: 'Alan', lastName: 'Pikard', age: 33, joinedDate: 2018-04-10T21:00:00.000Z } ]
Using a sorting Comparison function, accepting any type
Finally, an alternative option for implementing a more generic sort function like the one above is to use a separate Comparison function, similar to the one we show earlier at the beginning of this post.
Please recall, that a Comparison function passed in the built-in .sort() method for arrays, should always return a number: 1, -1, or 0 when we want to sort the array in ascending order. That returning number (1, -1, or 0) comes up as a result of comparing 2 elements of the same type (most of the time this is about comparing 2 numbers or 2 strings). When we want to sort the array in descending order, those numbers are the opposite ones: -1, 1, or 0. So we can implement just one Comparison function for ascending order, and then, if the task is to sort the array in descending order, we can simply reverse the returning number.
Such a Comparison function can be implemented very easily, and earlier in this post, we have seen one implementation of it:
function compareArrayOfNumbersAsc(n1: number, n2:number) :number { if (n1 > n2) { return 1; } if (n1 < n2) { return -1; } return 0; }
Now we can make it more generic, accepting any types, and we can make it, just in one line of code, using the ternary operator:
function compareAsc(v1: any, v2: any): number{ return v1 > v2 ? 1 : v1 < v2 ? -1 : 0; }
Moreover, we can even implement it as const (number), like that:
const compareAsc(v1:any, v2:any) => v1 > v2 ? 1 : v1 < v2 ? -1 : 0;
On the basis of the above const Comparison function ‘compareAsc’ we can re-write our example sorting function sort1, like that:
function sort1(employees: Emp[], field: string, direction: SortDirection): Emp[] { let sortedEmployees: Emp[] = []; if (direction === '') { return employees; } else { sortedEmployees = [...employees].sort((a,b) => { const res = compareAsc(a[field as keyof Emp], b[field as keyof Emp]); return direction === 'desc'? -res : res; }); } return sortedEmployees; }
The result of testing it:
console.log(sort2(EMPLOYEES,"joinedDate", "desc"));
is -as expected- pretty straightforward:
[ { firstName: 'John', lastName: 'Ripper', age: 27, joinedDate: 2020-11-15T22:00:00.000Z }, { firstName: 'Ana', lastName: 'Karenina', age: 22, joinedDate: 2019-03-24T22:00:00.000Z }, { firstName: 'Alan', lastName: 'Pikard', age: 33, joinedDate: 2018-04-10T21:00:00.000Z } ] [nodemon] clean exit - waiting for changes before restart
That’s it for now!
Thanx for reading!