skip to content

What must a comparator passed to Array.prototype.sort return, and what goes wrong if you write (a, b) => a > b?

level: middleimportance: must knowfreq 66%

answer

  1. only the sign of the return value matters
  2. negative, zero, positive — not a boolean
  3. booleans coerce to 1 or 0
  4. no way to say "a comes first"
  5. NaN return is treated as zero

basics

~20 s

A sort comparator must return a negative number to put a first, zero for a tie, and a positive number to put b first. Returning a boolean, as (a, b) => a > b does, gives only 1 or 0 — never negative — so the order is wrong.

solid answer

~50 s

`Array.prototype.sort` interprets only the **sign** of the comparator's return value: negative means `a` sorts before `b`, positive means `b` sorts before `a`, and zero means they are equivalent. The magnitude is ignored, which is why `a - b` works. `(a, b) => a > b` returns a boolean; the spec converts it with `ToNumber`, so `true` becomes `1` and `false` becomes `0`. The comparator can therefore never say "`a` comes first" — it only ever says "swap" or "equal" — and the result is an order the spec does not define. Two more contract rules matter: a return value of `NaN` (or `undefined`, which converts to `NaN`) is treated as `+0`, so those pairs are silently considered equal; and the comparator must be consistent — antisymmetric and transitive — or the resulting order is implementation-defined rather than merely surprising.

code

javascript · 7 lines
javascript
const words = ['pear', 'fig', 'apple', 'kiwi', 'date', 'plum', 'lime', 'melon'];

// Broken: returns true/false, which becomes 1/0 — never negative.
console.log([...words].sort((a, b) => a > b));

// Correct: a signed number.
console.log([...words].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)));

go deeper

for a junior

Memorise the three cases: negative keeps a first, zero means a tie, positive moves b first. Recognise that returning a comparison operator's boolean result is a bug, not a shortcut.

for a middle

Explain the coercion that breaks the boolean form — ToNumber turns true into 1 and false into 0 — and that NaN returns are treated as zero. Note that an invalid comparator produces an implementation-defined order rather than an error.

for a senior

Show how this reaches production: a comparator that is correct on clean data and inconsistent on real data, passing small tests and failing on full datasets. Describe how you normalise fields and choose an explicit position for missing values.

for a principal

Own the guardrails: a shared comparator utility with a defined null policy, property-based tests that assert antisymmetry and transitivity over generated data, and lint rules that flag comparators returning boolean expressions.

## The contract A comparator is a function of two elements whose **sign** answers one question: which of these two comes first? | Return value | Meaning | | --- | --- | | negative | `a` is placed before `b` | | `0` | `a` and `b` are equivalent for ordering purposes | | positive | `b` is placed before `a` | Only the sign is consulted. `-1` and `-9007199254740991` mean exactly the same thing to `sort`, which is why the subtraction idiom `(a, b) => a - b` works: the difference happens to carry the correct sign. ```js [3, 1, 2].sort((a, b) => a - b); // [1, 2, 3] [3, 1, 2].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); // same result ``` ## Why (a, b) => a > b is broken The relational operator produces a boolean. The sorting algorithm converts whatever the comparator returns to a number, so `true` becomes `1` and `false` becomes `0`. The comparator now has only two possible answers: "`b` first" and "equivalent". It has no way to express "`a` first". ```js [3, 1, 2].sort((a, b) => a > b); // unreliable — do not depend on this ``` What makes this so dangerous is that it often *looks* correct. Engines are free to choose their algorithm, and for a short array the path taken may still produce a sorted result, so a three-element unit test passes. On a longer array the same comparator produces visibly wrong output. The spec's position is blunt: if the comparator is not a consistent comparison function, the sort order is implementation-defined. The same trap appears in `(a, b) => a.name > b.name` and `(a, b) => a.date > b.date`. The fix is always to return a signed number: ```js const byName = (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0); ``` ## NaN and undefined returns The sorting operation converts the comparator's result with `ToNumber` and, if that value is `NaN`, uses `+0` instead. Two practical consequences: - A comparator that forgets a `return` statement returns `undefined`, which becomes `NaN`, which becomes `0`. Every pair compares equal, so the array comes back in its original order and no error is thrown. - A numeric comparator run over dirty data — `a.price - b.price` where a price is `null`, a string, or missing — produces `NaN` for exactly those pairs. Those elements are declared "equal to everything they touch", which is not just wrong but *inconsistent*, and the whole ordering degrades unpredictably. ```js const rows = [{ p: 5 }, { p: undefined }, { p: 1 }]; rows.sort((a, b) => a.p - b.p); // NaN for any pair involving the middle row ``` ## Consistency: antisymmetry and transitivity Beyond the sign rule, `sort` assumes your comparator behaves like a real ordering: - **Antisymmetric**: if `cmp(a, b)` is negative, `cmp(b, a)` must be positive. - **Transitive**: if `a` precedes `b` and `b` precedes `c`, then `a` must precede `c`. The same must hold for the "equal" relation. - **Pure and stable in time**: comparing the same pair twice must give the same answer. `(a, b) => Math.random() - 0.5` is the textbook violation — it is not a shuffle, it is undefined behaviour that also produces a heavily biased distribution. Violate any of these and you get no exception, no warning, just an order the specification declines to define. ## Type requirements The comparator argument itself must be `undefined` or callable. Passing anything else throws a `TypeError` before any comparison happens: ```js [3, 1, 2].sort('asc'); // TypeError ``` That is the one contract violation the language actually reports. ## How to write a comparator you can trust 1. Return a number, never a boolean. 2. Guarantee the value is a real number for every pair — normalise or coerce the field before comparing, and decide explicitly where missing values go. 3. Keep it pure: no reading of mutable outer state that could change mid-sort. 4. Test it on an input longer than a handful of elements, since short arrays can hide an invalid comparator.

  • What happens if the comparator returns undefined for some pairs?
    The return value is converted with `ToNumber`, giving `NaN`, and the sorting operation substitutes `+0` for `NaN`. Those pairs are therefore treated as equal. A comparator missing a `return` statement silently reports every pair as equivalent, so the array comes back in its original order with no error raised.
  • Is arr.sort(() => Math.random() - 0.5) a valid way to shuffle an array?
    No. It violates the comparator contract twice over — it is neither antisymmetric nor consistent between calls — so the resulting order is implementation-defined. It also produces a badly non-uniform distribution because different engines compare different pairs. Use a Fisher-Yates shuffle, which swaps elements directly and is provably uniform.
  • Does sort throw if the comparator is invalid?
    Only for one case: if the argument is neither `undefined` nor callable, `sort` throws a `TypeError` before comparing anything. A callable comparator that returns booleans, `NaN`, or inconsistent answers throws nothing — the spec simply leaves the resulting order implementation-defined, which is why these bugs reach production.

saying these in an interview costs you the question

  • Saying the comparator should return true or false
  • Believing the magnitude of the return value affects placement
  • Assuming a bad comparator throws an error
  • Calling Math.random() - 0.5 a correct shuffle
  • Trusting a three-element test to validate a comparator

context