Mastering JavaScript Array Sorting: Array.prototype.sort() with Examples
Updated on · 8 min read|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:
javascriptconst fruits = ["banana", "apple", "mango", "kiwi"]; fruits.sort(); console.log(fruits); // Output: ["apple", "banana", "kiwi", "mango"]
javascriptconst 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
.
javascriptconst numbers = [10, 5, 100, 30]; numbers.sort((a, b) => a - b); console.log(numbers); // Output: [5, 10, 30, 100]
javascriptconst 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:
javascriptconst fruits = ["banana", "apple", "Mango", "kiwi"]; fruits.sort(); console.log(fruits); // Output: ['Mango', 'apple', 'banana', 'kiwi']
javascriptconst 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.
javascriptconst fruits = ["banana", "apple", "Mango", "kiwi"]; fruits.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" })); console.log(fruits); // Output: ['apple', 'banana', 'kiwi', 'Mango']
javascriptconst 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.
In most cases, the localeCompare()
method is the best choice for sorting strings, especially when dealing with multilingual data. However, when dealing with complex strings containing Unicode characters, emojis, or other special characters, the localeCompare()
method may not always produce the expected results. In such cases, it may be necessary to use a more advanced string comparison methods, like Unicode normalization, to achieve the desired sorting behavior.
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:
javascriptconst 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 }, ]; */
javascriptconst 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:
javascriptconst 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 }, ]; */
javascriptconst 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.
jsconst 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" }, ]; */
jsconst 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.
javascriptconst 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' }, ]; */
javascriptconst 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(), which was included in the ECMAScript 2023 version and has a broad browser support.
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
- Can I Use: Array.prototype.toSorted
- Diacritic-insensitive String Comparison In JavaScript
- Immutable Array Operations In JavaScript: Introducing ToSorted, ToSpliced, and ToReversed
- MDN: Array.prototype.sort()
- MDN: Array.prototype.toSorted()
- MDN: Intl.Collator() constructor parameters
- MDN: Logical OR operator
- MDN: String.prototype.charCodeAt()
- MDN: String.prototype.localeCompare()
- Removing Duplicates with Map In JavaScript
- Simplifying Code with Maps In JavaScript and React
- Wikipedia: ASCII