Mastering JavaScript Array Sorting: Array.prototype.sort() with Examples

Updated on · 8 min read
Mastering JavaScript Array Sorting: Array.prototype.sort() with Examples

JavaScript is a versatile language that provides developers with an array of built-in methods to manipulate data, including sorting arrays. In this post, we explore the intricacies of the Array.prototype.sort() method, demonstrating its usage in various scenarios and exploring its limitations and caveats. By the end of this article, you will have a deep understanding of sorting arrays in JavaScript, including handling single and multiple sorting criteria, case-sensitive and case-insensitive sorting, and managing the consequences of in-place sorting.

Basic usage

The Array.prototype.sort() method is a built-in function provided by JavaScript to conveniently sort the elements of an array. By default, the sort() method sorts the array elements in ascending order, treating them as strings and comparing their sequence of UTF-16 code unit values. To use the sort() method, simply call it on the array you want to sort. For example:

javascript
const fruits = ["banana", "apple", "mango", "kiwi"]; fruits.sort(); console.log(fruits); // Output: ["apple", "banana", "kiwi", "mango"]
javascript
const fruits = ["banana", "apple", "mango", "kiwi"]; fruits.sort(); console.log(fruits); // Output: ["apple", "banana", "kiwi", "mango"]

While the default behavior of the sort() method may be sufficient for simple cases, it often requires customization to handle specific sorting requirements. In the following sections, we will discuss how to customize the sort() method using a compare function and explore various sorting scenarios to better understand its usage and capabilities.

The role of the comparison function

The comparison function plays a crucial role in customizing the sorting behavior of the sort() method in JavaScript. By providing a custom comparison function as an argument to the sort() method, we can define the exact sorting criteria for their specific use cases. The comparison function should accept two arguments, typically referred to as a and b, and return a negative, positive, or zero value depending on the desired order. If the returned value is less than 0, a should come before b; if greater than 0, a should come after b; and if equal to 0, the order of a and b doesn't matter. This flexible approach allows for precise control over the sorting process, enabling the sorting of numbers, strings, objects, or any custom data type, as well as handling ascending or descending order.

Numeric sorting

By default, the Array.prototype.sort() method treats elements as strings, which can lead to unexpected results when sorting numbers. For example, sorting an array of integers like [10, 5, 100, 30] would produce [10, 100, 30, 5], which is not what's expected.

This happens because the elements are treated as strings and compared lexicographically based on their UTF-16 code unit values (to get a UTF-16 code for a string, you can use the String.prototype.charCodeAt() method). In this case, "30" comes before "5" because the first character "3" is less than "5" when transformed to UTF-16. To correctly sort numbers, we need to provide a custom comparison function. This function doesn't need to return 0, -1, or 1 like when sorting string values, but instead can subtract b from a.

javascript
const numbers = [10, 5, 100, 30]; numbers.sort((a, b) => a - b); console.log(numbers); // Output: [5, 10, 30, 100]
javascript
const numbers = [10, 5, 100, 30]; numbers.sort((a, b) => a - b); console.log(numbers); // Output: [5, 10, 30, 100]

In this example, the comparison function (a, b) => a - b calculates the difference between the two numeric values, ensuring that they are sorted in ascending order. If you want to sort the numbers in descending order, simply reverse the operation: (a, b) => b - a. This straightforward approach to numeric sorting can be extended to handle floating-point numbers as well.

Alphabetical sort

When we sorted our fruits array, the default lexicographic sort was sufficient because all the strings were ASCII characters and had the same case. Consider a different array:

javascript
const fruits = ["banana", "apple", "Mango", "kiwi"]; fruits.sort(); console.log(fruits); // Output: ['Mango', 'apple', 'banana', 'kiwi']
javascript
const fruits = ["banana", "apple", "Mango", "kiwi"]; fruits.sort(); console.log(fruits); // Output: ['Mango', 'apple', 'banana', 'kiwi']

In this case, the order is incorrect because the capital "M" comes before all the lowercase characters on the UTF-16 scale. Additionally, if the array had any strings in languages other than English, the sorting results would also be incorrect.

To fix this, we can use the String.prototype.localeCompare method, which returns a number indicating whether a reference string comes before, or after, or is the same as the given string in sort order. Additionally, it accepts a third options parameter, which has a sensitivity property. Setting it to base will use the case-insensitive sort.

javascript
const fruits = ["banana", "apple", "Mango", "kiwi"]; fruits.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" })); console.log(fruits); // Output: ['apple', 'banana', 'kiwi', 'Mango']
javascript
const fruits = ["banana", "apple", "Mango", "kiwi"]; fruits.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" })); console.log(fruits); // Output: ['apple', 'banana', 'kiwi', 'Mango']

Setting the sensitivity option to base will perform a case-insensitive sort, but it will also ignore accent differences between characters. If you want to perform a case-insensitive sort while still considering accent differences, you should set the sensitivity option to accent instead.

In some modern browsers like Chrome, the localeCompare() method, by default, performs case-insensitive sorting based on the browser's locale, which may yield the expected result even without specifying the sensitivity option. However, this behavior is not guaranteed across all environments and browser versions, as the default behavior may be different for other browsers or older versions.

If you're curious about working with the Map data structure in JavaScript, you may find these posts helpful: Removing Duplicates with Map In JavaScript and Simplifying Code with Maps In JavaScript and React.

Sorting an array of objects by object property

Sorting an array of objects based on a specific property value is a common requirement in JavaScript development. To achieve this, a custom comparison function can be provided to the Array.prototype.sort() method. The function compares the property values of the two objects and sorts them accordingly.

Let's consider an example where we have an array of objects that represent people with name and age properties:

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

In this example, the comparison function (a, b) => a.age - b.age sorts the objects based on their age property in ascending order.

Sorting an array of objects by multiple properties

When you need to sort an array of objects based on multiple properties, you can extend the custom comparison function to compare additional properties when the first property values are equal. To do this, you can use the logical OR operator to define the sorting criteria for each property in order of priority:

javascript
const people = [ { name: "Alice", age: 30 }, { name: "Bob", age: 30 }, { name: "Charlie", age: 35 }, { name: "David", age: 30 }, ]; people.sort((a, b) => a.age - b.age || a.name.localeCompare(b.name)); console.log(people); /* Output: [ { name: "Alice", age: 30 }, { name: "Bob", age: 30 }, { name: "David", age: 30 }, { name: "Charlie", age: 35 }, ]; */
javascript
const people = [ { name: "Alice", age: 30 }, { name: "Bob", age: 30 }, { name: "Charlie", age: 35 }, { name: "David", age: 30 }, ]; people.sort((a, b) => a.age - b.age || a.name.localeCompare(b.name)); console.log(people); /* Output: [ { name: "Alice", age: 30 }, { name: "Bob", age: 30 }, { name: "David", age: 30 }, { name: "Charlie", age: 35 }, ]; */

In this example, the comparison function first compares the age property values. If they are equal, it then compares the name property values using localeCompare() to ensure lexicographic sorting. The objects are sorted based on their age property in ascending order, and if their ages are equal, they are sorted by their name property in ascending order.

Sorting an array of objects by object property in a specific order

There may be instances where you need to sort an array of objects in a specific order. For instance, you might want to sort people by their addresses in a particular way. To achieve this, we define an addressOrder array that specifies the order of addresses and create a compareObjects function that gets the index of the compared property in the addressOrder array.

js
const data = [ { name: "John", age: 30, address: "123 Main St" }, { name: "Jane", age: 25, address: "456 Elm St" }, { name: "Bob", age: 40, address: "789 Oak St" }, ]; const addressOrder = ["456 Elm St", "123 Main St", "789 Oak St"]; const compareObjects = (a, b) => { const aIndex = addressOrder.indexOf(a.address); const bIndex = addressOrder.indexOf(b.address); return aIndex - bIndex; }; data.sort(compareObjects); console.log(data); /* Output: [ { name: "Jane", age: 25, address: "456 Elm St" }, { name: "John", age: 30, address: "123 Main St" }, { name: "Bob", age: 40, address: "789 Oak St" }, ]; */
js
const data = [ { name: "John", age: 30, address: "123 Main St" }, { name: "Jane", age: 25, address: "456 Elm St" }, { name: "Bob", age: 40, address: "789 Oak St" }, ]; const addressOrder = ["456 Elm St", "123 Main St", "789 Oak St"]; const compareObjects = (a, b) => { const aIndex = addressOrder.indexOf(a.address); const bIndex = addressOrder.indexOf(b.address); return aIndex - bIndex; }; data.sort(compareObjects); console.log(data); /* Output: [ { name: "Jane", age: 25, address: "456 Elm St" }, { name: "John", age: 30, address: "123 Main St" }, { name: "Bob", age: 40, address: "789 Oak St" }, ]; */

Since the indices are numeric values, we can use a simplified comparison function for numeric comparison.

Note that this implementation assumes that all address properties in the data array are included in the addressOrder array. If the data array contains objects with properties that are not in the addressOrder array, the compareObjects function will need to be modified to account for that.

javascript
const data = [ { name: "John", age: 30, address: "123 Main St" }, { name: "Jane", age: 25, address: "456 Elm St" }, { name: "Bob", age: 40, address: "789 Oak St" }, { name: "Alice", age: 35, address: "1001 Maple St" }, ]; const addressOrder = ["456 Elm St", "123 Main St", "789 Oak St"]; const compareObjects = (a, b) => { const aIndex = addressOrder.indexOf(a.address); const bIndex = addressOrder.indexOf(b.address); if (aIndex === -1 && bIndex !== -1) { return 1; } else if (aIndex !== -1 && bIndex === -1) { return -1; } else if (aIndex === -1 && bIndex === -1) { return a.address.localeCompare(b.address); } else { return aIndex - bIndex; } }; data.sort(compareObjects); console.log(data); /* Output: [ { name: "Jane", age: 25, address: "456 Elm St" }, { name: "John", age: 30, address: "123 Main St" }, { name: "Bob", age: 40, address: "789 Oak St" }, { name: 'Alice', age: 35, address: '1001 Maple St' }, ]; */
javascript
const data = [ { name: "John", age: 30, address: "123 Main St" }, { name: "Jane", age: 25, address: "456 Elm St" }, { name: "Bob", age: 40, address: "789 Oak St" }, { name: "Alice", age: 35, address: "1001 Maple St" }, ]; const addressOrder = ["456 Elm St", "123 Main St", "789 Oak St"]; const compareObjects = (a, b) => { const aIndex = addressOrder.indexOf(a.address); const bIndex = addressOrder.indexOf(b.address); if (aIndex === -1 && bIndex !== -1) { return 1; } else if (aIndex !== -1 && bIndex === -1) { return -1; } else if (aIndex === -1 && bIndex === -1) { return a.address.localeCompare(b.address); } else { return aIndex - bIndex; } }; data.sort(compareObjects); console.log(data); /* Output: [ { name: "Jane", age: 25, address: "456 Elm St" }, { name: "John", age: 30, address: "123 Main St" }, { name: "Bob", age: 40, address: "789 Oak St" }, { name: 'Alice', age: 35, address: '1001 Maple St' }, ]; */

In this example, we have an object for 'Alice' with an address '1001 Maple St', which is not present in the addressOrder array. The compareObjects function now checks if the index values are -1. If both objects' addresses are not found in the addressOrder array, it sorts them lexicographically using localeCompare(). If only one address is not found, it places that object at the end of the sorted array.

Limitations and caveats of Array.prototype.sort()

While the Array.prototype.sort() method is a versatile and convenient way to sort arrays in JavaScript, it has some limitations and caveats that developers need to be aware of.

Firstly, as mentioned earlier, the default sorting behavior is lexicographic, which may produce unexpected results for numeric or mixed-type arrays. To obtain the desired sorting order, a custom comparison function is often necessary.

Secondly, the sort() method is not guaranteed to be stable, meaning that elements with equal sorting keys might not retain their original order. Although some JavaScript engines, like V8, have implemented stable sorting, this behavior is not consistent across all environments.

Thirdly, the performance of the sort() method varies depending on the JavaScript engine and the sorting algorithm used. In some cases, the built-in sorting method might not be the most efficient solution for large datasets or specific sorting requirements.

Lastly, the sort() method performs in-place sorting, which means that it modifies the original array instead of creating a new, sorted array. While this approach is memory-efficient and often desirable, it also has potential consequences that developers should be aware of. One consequence is the loss of the original order of elements, as the original array is directly modified during the sorting process. In cases where preserving the original order is necessary, it is advisable to create a copy of the array before sorting. Another consequence is that in-place sorting can inadvertently impact other parts of the application when multiple references to the same array exist.

It should be noted that an immutable version of the sort() method exists - Array.prototype.toSorted(), although its support is limited in some major browsers. It has, however, reached the stage 4 of the TC39 Process and is expected to be included in the upcoming ECMAScript version - ECMAScript 2023.

Conclusion

Mastering the art of sorting arrays in JavaScript is a valuable skill for any developer. Throughout this guide, we have explored the ins and outs of the Array.prototype.sort() method, including its basic usage, custom comparison functions, and addressing limitations and caveats. By understanding and applying these concepts, you can effectively sort arrays in JavaScript, tailoring the sorting logic to meet the needs of your applications. As you continue to enhance your JavaScript skills, remember to consider the impact of your sorting choices and explore alternative techniques and libraries to achieve the most efficient and accurate sorting results.

References and resources