Ch. 1 · JavaScript

JavaScript Array Sort Comparators

Sort arrays in place with a correct comparator, understand default string ordering and sort stability, and avoid the numeric and string traps.

~3 min readbeginnerupdated Oct 4, 2026

Array.prototype.sort mutates the array in place and, without a comparator, converts every element to a string and sorts lexicographically. Supplying a comparator makes the ordering explicit, but the comparator has to satisfy a simple contract for the result to be well defined.

Before you start

You should be comfortable with arrays, Array.prototype.sort, and returning numbers from functions. This article is about the comparator contract and stability; it is not an algorithm analysis of the sort implementation.

Step-by-step walkthrough

Step 1: Always pass a comparator for numbers

The default converts elements with String, so [10, 9, 80].sort() yields [10, 80, 9] because "10" < "80" < "9". A numeric comparator (a, b) => a - b returns a negative number when a sorts first, positive when b first, and zero for equal order.

Step 2: Keep the comparator pure and total

The comparator should depend only on its two arguments and return a consistent sign for the same pair; it must not mutate the array or rely on external state. a - b on strings evaluates to NaN, which the engine treats as zero and leaves the order unpredictably unchanged. Use a.localeCompare(b) or explicit comparisons for text.

Step 3: Lean on stability for tie-breaking

Modern engines implement a stable sort, so elements that compare equal keep their relative order. That makes a single sort key safe: sort once by the primary value and rely on the pre-existing order for ties, or write a composite comparator that breaks ties explicitly when the input order is not guaranteed.

Worked scenario

Run this with Node.js. The two elements with score 2 keep their original relative order.

const rows = [
  { name: 'b', score: 2 },
  { name: 'a', score: 2 },
  { name: 'c', score: 1 }
];
rows.sort((a, b) => a.score - b.score);
console.log(rows.map((row) => row.name)); // ['c', 'b', 'a']
JavaScript

Walk through the example

The comparator compares only score, so c moves ahead of the two score-2 rows, and because the sort is stable, b stays before a. The array is sorted in place: rows itself is reordered, not a copy. If the original order were undefined, the result for the tied rows would be unspecified, which is why an explicit tie-break matters for deterministic output.

Common mistake

Calling .sort() on numbers and expecting numeric order, or using a - b on strings and trusting the result. Another frequent slip is assuming sort returns a new array: it returns the same, mutated array, so code that still references the original sees the reordered values.

Verify the behavior

Assert the numeric order for a mixed array such as [10, 9, 80]. Assert that equal keys preserve input order for a stable-expected case. Test empty and single-element arrays, and confirm that a comparator that returns NaN (from string subtraction) does not accidentally reorder numeric-looking strings. If you need an immutable result, copy first with [...items].sort(...).

Interview exercise

Sort records by score descending, and by name ascending when scores are equal.

Answer and reasoning

Return b.score - a.score for the primary key. When that is zero, fall back to a.name.localeCompare(b.name) for the secondary key. Writing the tie-break in the comparator makes the result deterministic regardless of the input order, instead of relying on stability that only holds for equal keys and depends on how the array was built.

Follow-up discussion

Is the sort stable everywhere? Since ES2019 it is required to be stable, but older engines may not be, so do not rely on stability for correctness-critical ordering. Does a bad comparator crash? No: an inconsistent comparator produces an implementation-defined order rather than an error, which is why a subtly wrong sign is easy to miss in review.

Continue learning

Compare value-comparison subtleties in JavaScript equality and coercion and read the MDN sort reference. Try the JavaScript interview questions.

More in JavaScript

read ✓JavaScript · mid

JavaScript Date and Timezone Pitfalls

Parse and format dates without timezone surprises: zero-based months, date-only versus date-time parsing, and why UTC storage avoids drift.

~3 min readread →
read ✓JavaScript · mid

JavaScript Number Precision and BigInt

Why 0.1 + 0.2 is not 0.3, where safe integer range ends, and when BigInt is the right tool for large identifiers and money.

~2 min readread →
esc