# How to sort arrays reliably in TypeScript

Let's say you have an array of numbers and need to sort them; it's easy to just call `.sort()` and be on your way, like this:

```typescript
const numbers = [10, 25, 2, 4, 1, 100, 6]

numbers.sort()
```

You would probably assume that since the values are numbers, it would sort them from smallest to biggest, right? Unfortunately, that's not the case and you end up with:

```typescript
console.log(numbers)

// expected: [1, 2, 4, 6, 10, 25, 100]
// actual: [1, 10, 100, 2, 25, 4, 6]
```

The [sort method's default comparison function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#description) converts values to strings before comparing them. So it converts the numbers to strings and then compares them alphabetically.

Since the default sort comparison converts values to strings and then compares them, you'd assume that it's safe to rely on it when sorting an array of strings, right? Unfortunately, that's not the case here either:

```typescript
const letters = ['a', 'A', 'b', 'B']

letters.sort()
// expected: ['a', 'A', 'b', 'B']
// actual: ['A', 'B', 'a', 'b']
```

The default compare function sorts strings based on their [UTF-16 values](https://string-functions.com/encodingtable.aspx?encoding=1200&decoding=20127). This means that all uppercase characters are sorted before lowercase characters, and characters with accents are also sorted after characters without accents.

## Customizing how an array is sorted

Since the default way arrays are sorted probably isn't what you want in practice, let's see how we can customize it so that it works the way you'd expect.

The `.sort()` method accepts a single parameter, which is called a *compare function*. This compare function should expect to be passed two values from the array and should return a number that indicates how those two items should be sorted. The `.sort()` method then calls this compare function to compare each item in the array to properly sort it.

If you were to define a type for the compare function, it would look like this:

```typescript
interface SortCompareFunction<ArrayItemType> {
  (firstItem: ArrayItemType, secondItem: ArrayItemType): number
}
```

The way that the items are sorted depends on the number that the compare function returns and is based on if it's 0, positive, or negative.

| Return Value | Sort Order |
| --- | --- |
| Number greater than zero (&gt; 0) | First item should appear first |
| Number less than zero (&lt; 0) | Second item should appear first |
| Number equals zero (=== 0) | Maintain original sort order |

### How to sort numbers

Since the compare function is expected to return a number, you can just subtract one number from the other and return the difference. If the values are the same then it'll return 0, if the first is bigger, it will return a positive number, otherwise, it will return a negative number.

```typescript
function compareNumbersInAscendingOrder(firstNumber: number, secondNumber: number) {
  return firstNumber - secondNumber
}

function compareNumbersInDescendingOrder(firstNumber: number, secondNumber: number) {
  return secondNumber - firstNumber
}

numbers.sort(compareNumbersInAscendingOrder)
// output: [1, 2, 4, 6, 10, 25, 100]

numbers.sort(compareNumbersInDescendingOrder)
// output: [100, 25, 10, 6, 4, 2, 1]
```

### How to sort strings

Strings come with their own [`localeCompare` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) which can be used to sort strings how you might expect. It's based on the [`Int.Collator` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator#options) and accepts all the same options so that you can further customize how strings are compared.

```typescript
function compareStringsInAscendingOrder(firstString: string, secondString: string) {
  // Set the locale based on the string's locale, otherwise set to undefined to use the browser's locale.
  return firstString.localeCompare(secondString, 'en', { caseFirst: 'upper' })
}

function compareStringsInDescendingOrder(firstString: string, secondString: string) {
  return secondString.localeCompare(firstString, 'en', { caseFirst: 'upper' })
}

letters.sort(compareStringsInAscendingOrder)
// output: ["A", "a", "B", "b"] 

letters.sort(compareStringsInDescendingOrder)
// output: ["b", "B", "a", "A"] 
```

Depending on how often you're sorting and how big the array is that you're sorting, you may run into some performance issues since each time the `localeCompare` is called a new instance of the `Intl.Collator` is created. You can resolve this by creating your own instance of the `Intl.Collator` once and then just referencing it.

```typescript
const enCollator = new Intl.Collator('en', { caseFirst: 'upper' })

hugeListOfWordsThatNeedSorting.sort(enCollator.compare)
```

## Reusing compare functions

While the examples above declare the compare functions on their own, it's more common to see the compare function declared directly inside the `.sort()` method as an anonymous function like so:

```typescript
numbers.sort((firstNumber, secondNumber) => {
  return firstNumber - secondNumber
})
```

This can be good for one-off and shorter compare functions, but if you're doing a lot of sorting it can be beneficial to share compare functions from a common location so they can be re-used throughout your app since there's a good chance that many of them can be highly reusable.

```typescript
import { byNumberAsc } from '~/utils/sort'

numbers.sort(byNumberAsc)
```

## Eslint rule

If you use [ESLint](https://eslint.org/) with [typescript-eslint](https://typescript-eslint.io/), there's actually a rule called [`require-array-sort-compare`](https://typescript-eslint.io/rules/require-array-sort-compare/) you can enable to help make sure you never forget to provide a compare function when sorting. It can be easy to forget and have your app work locally when testing with trivial data only to find out things don't sort properly for real users with real data.
