All Articles

Understanding Javascript's Array.prototype.sort beyond 1 to 9

Sorting

If you have programmed in javascript, chance’s are you would have come across Array.prototype.sort. Sometimes it does what you want, other times it does what it wants. In this article we will try to understand Array.prototype.sort beyond 1 - 9.

If you have had to use this method, like me you can recall having to tweak operators to do what you want. Well, try a + b first (which never does what you want), then a - b, occasionally an explicit ternary πŸ˜„.

It feels very intuitive and seemingly simple to use but not quite straight-forward to understand. Here is the signature;

const array = [1,4,3,2,7]
array.sort((a, b) => a - b);
  • a - first element, or left hand side element
  • b - second element, or right hand side element

The comparator (a,b) => a - b is expected to return

  • A negative value if a < b
  • A positive value if a > b
  • Zero if the first and second arguments are equal. Thus a = b
const positions = [1,5,7,3,9]
const sorted = [...positions].sort((a, b) => a - b)
console.log(sorted) // [1, 3, 5, 7, 9]

//OR

const sortWithoutCompareFn = [...positions].sort()
console.log(sortWithoutCompareFn) // [1, 3, 5, 7, 9]

Weirdness beyond One, Two, Three

Sort without a comparator

const positions = [1,5,7,3,9, 10, 100]
positions.sort()
console.log(positions) // [1, 10, 100, 3, 5, 7, 9] ❗️

Briefly, if the comparator is not provided, then the sort order is ascending/alphabetical. The caveat is that, numbers are converted to strings and their UTF-16 unit values are compared hence the unintuitive position of 10 and 100 above. Basically β€œ1” (β€œ1”, β€œ10”, β€œ100”) is smaller that β€œ3”.

If you are keen on how the abstract relation comparison works, step 3c. This is easy to fix by providing a compare function.

Recap Sort with a comparator

const positions = [1,5,7, 10, 100, 3,9]
const sorted = [...positions].sort((a, b) => a - b)
console.log(sorted) // [1, 3, 5, 7, 9, 10, 100]

This behaviour is fairly straight-forward and will behave as intended as long as your comparator returns a number. The expression Number(campareFn(a,b)) should not evaluate to NaN otherwise a and b are considered equal thus 0 is returned !.

const positions = [1,5,7, 'a', 10, 100, 3,9]
const sorted = [...positions].sort((a, b) => a - b)
console.log(sorted) // [1, 5, 7, "a", 3, 9, 10, 100] ❗️

This again is easy to fix by providing a compare function

const positions = [1,5,7, 'a', 10, 100, 3,9]
const sorted = [...positions].sort((a, b) => a > b ? 1 : -1)
console.log(sorted) // ["a", 1, 3, 5, 7, 9, 10, 100] βœ…

The undefined

const positions = [1, 3, undefined, 5, 2]
const sorted = [...positions].sort((a, b) => a - b)
console.log(sorted) // [1, 2, 3, 5, undefined]

undefined always compare greater than any value. Here a few simple rules;

  • If a and b are both undefined, 0 is returned thus elements are equal.
  • If a is undefined, return 1.
  • If b is undefined, return -1.

Take away

To achieve a more deterministic or consistent result, always provide a compare function! Until next time, stay curious !

Published on January 2020