JavaScript Array sort Without a Comparator

When you call Array.prototype.sort() in JavaScript without providing a compare function, the method automatically converts all non-undefined array elements into strings and sorts them in ascending order based on their UTF-16 code unit values. This article covers how this default string conversion works, why it causes common bugs with numeric data, how special values like undefined are treated, and the in-place nature of the operation.

Default String Conversion and Lexicographical Sorting

By default, the sorting algorithm treats every element as a string. For elements that are not already strings (such as numbers, booleans, or objects), JavaScript internally calls their .toString() method before determining their order.

Once converted to strings, elements are compared character by character using their UTF-16 code unit values (lexicographical order).

const fruits = ['banana', 'apple', 'cherry'];
fruits.sort();
// Result: ['apple', 'banana', 'cherry']

The Numeric Sorting Pitfall

Because elements are converted to strings, sorting numbers without a comparator often produces counterintuitive results. Instead of sorting by numerical value, JavaScript compares the string representations of the numbers character by character.

For example, '10' comes before '2' because the character '1' has a lower UTF-16 code unit value than '2':

const numbers = [10, 5, 40, 25, 1000, 1];
numbers.sort();
// Result: [1, 10, 1000, 25, 40, 5]

To sort numbers by their actual mathematical value, you must supply a custom comparator function:

numbers.sort((a, b) => a - b);
// Result: [1, 5, 10, 25, 40, 1000]

Handling of undefined and Sparse Arrays

The default sorting algorithm handles undefined values and empty slots in a specific way:

  1. undefined values: All undefined elements are moved to the end of the array. The internal string conversion is not called on undefined, and it is not passed through lexicographical comparison with defined values.
  2. Sparse arrays (empty slots): Empty slots in sparse arrays are treated as undefined and are also moved to the end of the array.
const mixed = [undefined, 3, , 1, 'apple'];
mixed.sort();
// Result: [1, 3, 'apple', undefined, empty]

null, Booleans, and Objects

Elements such as null, true, false, and objects are converted to their standard string representations prior to sorting:

const values = [null, true, false, 10];
values.sort();
// Result: [10, false, null, true]
// (Because "10" < "false" < "null" < "true")

In-Place Mutation

Array.prototype.sort() mutates the original array directly rather than returning a new copy. It also returns the reference to the same mutated array. If you need to preserve the original array order, create a shallow copy before sorting:

const original = [3, 1, 2];
const sorted = [...original].sort();

// original remains: [3, 1, 2]
// sorted is: [1, 2, 3]