Sorting an array in JavaScript, a utility perspective!
Tags: javascript
Published: 2021-09-07 19:49:18

---
title: Sorting an array in JavaScript, a utility perspective!
published: true
description: 
tags: javascript
cover_image: https://images.unsplash.com/photo-1616628188538-0bc97a2f741b?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80
canonical_url: https://www.antoniovdlc.me/sorting-an-array-in-javascript-a-utility-perspective/
---

If you have written some JavaScript and manipulate slightly complex data, you have had to write some code like this to sort an array of objects:

```js
const data = [
  { name: "Alice", age: 22 },
  { name: "Bob", age: 32 },
  { name: "Carl", age: 63 },
  { name: "Clara", age: 28 },
  ...
];

data.sort(function(a, b) {
  if (a.name < b.name) {
    return -1;
  }
  
  if (a.name > b.name) {
    return 1;
  }
  
  return 0;
})

// Or, as a one-liner: 
data.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)
```

While this is perfectly fine for one-off sorting of shallow objects, it can get a bit more complex and repetitive when having to sort based on nested fields.

Something else you might have tripped on while using the native `.sort()` on arrays, is the following behaviour:

```js
const array = [1, 2, 3, 10, 23]
console.log(array.sort())
// [1, 10, 2, 23, 3]
```

Indeed, by default, the comparison function used by `.sort()` treats each element as a string! To make the above example work, you need to pass a custom comparison function such as the following one-liner:

```js
const array = [1, 23, 3, 10, 2]
console.log(array.sort((a, b) => a - b))
// [1, 2, 3, 10, 23]
```

---

As sorting is a common operation on arrays, a more scalable and less error-prone strategy would be to define common compare functions. Let's build said compare functions!

First, let's look at the API we would like to end up with:

```js
const array = [1, 23, 3, 10, 2]
array.sort(numerically)
// Should be equivalent to:
array.sort((a, b) => a - b)

array.sort(numerically.desc)
// Should be equivalent to:
array.sort((a, b) => b - a)
// For completeness, we can also expose `numerically.asc`.
```

To achieve the above API, we can define `numerically` as follows:
```js
function numerically (a, b) {
  return a - b;
}
```

As in JavaScript, (almost) everything is an object, we can then add a `desc` and an `asc` field to the `numerically` function as follows:

```js
numerically.desc = function(a, b) {
  return b - a;
}

numerically.asc = function(a, b) {
  return numerically(a, b); // This works because we sort from lower to higher by default!
}
```

---

Now that we have defined compare functions to work on arrays holding primitives values, let's generalise it to arrays of objects:

```js
const data = [
  { name: "Alice", age: 22 },
  { name: "Bob", age: 32 },
  { name: "Carl", age: 63 },
  { name: "Clara", age: 28 },
  ...
];

data.sort(alphabetically.by("name"))
// Should be equivalent to:
data.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)
```

To achieve that, let's create a small utility function that will help us retrieve the value of an object based on a key path:
```js
function getValueByKey(obj, key) {
  return String(key)
    .split(".")
    .reduce((acc, cur) => acc?.[cur] ?? null, obj);
}
```
With the code above, we can do deep object look-ups!

With that in hand, let's add the following to our example `alphabetically` sort function:
```js
function alphabetically (a, b) { ... }

alphabetically.desc = function(a, b) { ... }
alphabetically.asc = function(a, b) { ...}

alphabetically.by = function(key) {
  return function(a, b) {
    const aVal = getValueByKey(a, key);
	const bVal = getValueByKey(b, key);
	
	return a < b ? -1 : a > b ? 1 : 0;
  }
}
```

Alright, this works great for ascending order sorting, but how could we implement descending order? There are different ways of solving this:
- Pass another argument that can have either `"desc"` or`"asc"` values (defaults to `"asc"`)
- Append a `-` sign in the key (for example: `sort(alphabetically.by("-name")`)
- Add `.desc()` and `.asc()` functions to our new function `.by()`

Either designs are fine, but to stay consistent with our previous utility function, we will be adding the ordering feature as follow:

```js
data.sort(alphabetically.by("name").desc)
```

All implemented, it looks like:

```js
function alphabetically (a, b, direction = 1) {
  if (a < b) {
    return -1 * direction;
  }
  
  if (a > b) {
    return 1 * direction;
  }
  
  return 0;
}

alphabetically.asc = (a, b) => alphabetically(a, b, 1);
alphabetically.desc = (a, b) => alphabetically(a, b, -1);

alphabetically.by = function(key) {
  function compareBy(a, b, direction = 1) {
    const aVal = getValueByKey(a, key);
	const bVal = getValueByKey(b, key);
	
	return aVal < bVal ? -1 * direction : aVal > bVal ? 1 * direction : 0;
  }
  
  compareBy.asc = (a, b) => compareBy(a, b, 1);
  compareBy.desc = (a, b) => compareBy(a, b, -1);
  
  return compareBy;
}
```

---

I found this exercise particularly interesting, and decided to build a library with some of the ideas discussed in this post. You can have a look at it here:

{% github AntonioVdlC/sort %}