Sorting Complex Arrays in JavaScript

Hero image for Sorting Complex Arrays in JavaScript. Image by Patrick Hendry.
Hero image for 'Sorting Complex Arrays in JavaScript.' Image by Patrick Hendry.

Sorting arrays and manipulating data in JavaScript is a common, often complex and difficult, part of the work we do as frontend and fullstack developers. When working with complex data structures in particular, it requires a deeper understanding of how the sort() method behaves. Sorting arrays with objects, nested properties, or multiple criteria can be tricky, but JavaScript provides powerful tools to handle these scenarios effectively, as long as we understand how to properly use them...

Here, I will explore different techniques for sorting complex arrays in JavaScript, covering common use cases and best practices. By the end, you should have a strong grasp of how to manipulate and organise data efficiently.


Understanding the sort() Method

JavaScript's builtin Array.prototype.sort() method sorts elements in place and, by default, converts those elements to strings before comparing them. However, this behaviour is not nearly as useful if you are working with numbers or objects.

Basic Sorting with Numbers

Sorting a simple array of numbers might seem straightforward, however, JavaScript's sort() method does not handle numbers as you might expect by default. Let's start with an example:

const numbers = [10, 5, 8, 1, 7];numbers.sort();console.log(numbers);  // Output: [1, 10, 5, 7, 8] (incorrect sorting)

What I'm demonstrating here is the classic issue that sort() presents by default, 10 comes before 5 because sort() converts the elements passed to it to strings before comparing them. In fact, if we added another value like 101 to the array, even that would be placed before 5 when using sort() in it's default configuration.

In order to use sort() to correct sort numbers, we need to use it as a higherorder function and pass a comparison function into it. Like this:

numbers.sort((a, b) => a - b);console.log(numbers);  // Output: [1, 5, 7, 8, 10] (correct sorting)

The comparison function ensures numeric sorting by subtracting b from a. A negative return value means a should come before b, a positive value means b should come before a, and 0 keeps the order unchanged.


Sorting an Array of Objects

When dealing with an array of objects, it becomes a little more complicated; we must specify which property to sort by in our comparison:

const people = [  { name: "Maddie", age: 25 },  { name: "Bob", age: 30 },  { name: "Charlie", age: 20 }];people.sort((a, b) => a.age - b.age);console.log(people);// Output: [{ name: "Charlie", age: 20 }, { name: "Maddie", age: 25 }, { name: "Bob", age: 30 }]

In this way, the array is sorted in ascending order based on the age property.


Sorting Nested Properties

When we're dealing with deeply nested properties, we handle them in much the same way, but just by making sure that reference the correct nested property within the comparison function.

For example, if we have a nested dataset of devices and want to sort them by their price, it would look like this:

const products = [  { name: "Laptop", details: { price: 1000 } },  { name: "Phone", details: { price: 700 } },  { name: "Tablet", details: { price: 500 } }];products.sort((a, b) => a.details.price - b.details.price);

Really, this is much the same as the example above, we've just moved one level further down the nested data tree.


Sorting by Multiple Criteria

In some cases, sorting by a single property just simply isn't enough. For example, we may need to sort by age, but if two people have the same age, we also want to sort them alphabetically by name. I came across a similar requirement in sorting search results for Virgin Atlantic, where two flights leaving at the same time should also be sorted by the airline name.

Sticking with the peopleandage example for consistently, here's how we would handle this multicriteria sorting:

people.sort((a, b) => {  if (a.age === b.age) {    return a.name.localeCompare(b.name);  // Sort alphabetically  }  return a.age - b.age;  // Sort by age first});

This creates a stable sort where records with the same primary sorting key (age) are ordered by a secondary key (name), once sorted by age.


Case‑Insensitive String Sorting

Sorting by Unicode Value

A final 'complex sorting' example for today, and another quirk of stringbased sorting. By default, string sorting in JavaScript is casesensitive. This is because JavaScript uses unicode values when comparing strings, and uppercase letters (AZ) have lower unicode values than lowercase letters (az).

If, for example, we had Cat, cat, dog, and Dog in an array, then when sorted, the strings would come back in the order ["Car", "Dog", "car", "dog"]:

const words = ["cat", "Cat", "dog", "Dog"];words.sort();console.log(words);  // Output: ["Cat", "Dog", "cat", "dog"]

This is because the unicode values for the first letter of each word are:

  • C

    = U+0043
  • D

    = U+0044
  • c

    = U+0063
  • d

    = U+0064

In my experience, this trips up developers all the time, although hopefully it makes sense once you see it in action as above.

Sorting Without Case Sensitivity

Since sorting is based on Unicode values, we need to convert the elements of our array to the same case before attempting to compare them. Usually, this we use localeCompare() combined with toLowerCase() like this:

const words = ["banana", "Apple", "cherry"];words.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));console.log(words);  // Output: ["Apple", "banana", "cherry"]

Given what we've discussed above about sorting by unicode, then often simply converting the elements to lowercase for the sake of comparison is enough to standardise our input. However, combining this with localeCompare() makes sure that proper sorting occurs across different languages and handles more localespecific rules, which might otherwise fall outside of a simple character comparison.

To offer an example, in the Swedish alphabet ä comes after z, whereas in English, it could be treated as a variation of a and therefore fall immediately after it in sorting.

So, if we use localeCompare set to sv too, we get this:

const words = ["äpple", "apple", "banana"];words.sort((a, b) => a.localeCompare(b, "sv"));console.log(words);   // Output: ["apple", "banana", "äpple"]

Without using localeCompare() the sorting would be wrong: ["apple", "äpple", "banana"].


Wrapping up

Sorting complex arrays in JavaScript requires a solid grasp of the sort() method and how to use comparison functions effectively. By default, JavaScript sorts elements as strings using their unicode values, which can lead to unexpected results when working with numbers or mixedcase text.

We've also explored how JavaScript's default sorting behaviour is casesensitive and how localeCompare() provides a more robust approach, particularly when working with international character sets. Understanding these nuances allows for more reliable and maintainable sorting operations. Whether you're sorting numbers, objects, or nested properties, knowing how to structure your sorting logic is essential. Whether working with numbers, objects, or nested properties, structuring sorting logic correctly ensures predictable and efficient data handling.

Key Takeaways

  • JavaScript's sort() method sorts elements as strings by default, based on Unicode values, which can lead to unexpected results.
  • Sorting numbers requires explicit subtraction (a b) to ensure correct numerical ordering.
  • Object sorting relies on accessing the correct property and handling nested structures where necessary.
  • Multicriteria sorting enables prioritisation when sorting by multiple fields.
  • Using localeCompare() ensures caseinsensitive sorting and handles localespecific character rules correctly.
  • Understanding default sorting behaviour helps prevent unexpected results and ensures predictable data handling.

Sorting is a core part of working with arrays in JavaScript, and refining these techniques will help in structuring data efficiently across different applications.

  • Always provide a comparison function when sorting numbers.
  • Sorting objects requires specifying which property to compare.
  • Multicriteria sorting allows for prioritised ordering of values.
  • Use localeCompare() for proper caseinsensitive sorting.
  • Sorting nested properties requires carefully accessing the correct value before comparison.

Having a clear understanding of these sorting techniques will help you write more reliable and structured JavaScript code, ensuring your data is consistently organised.


Categories:

  1. Development
  2. Front‑End Development
  3. Guides
  4. JavaScript