While patching a legacy Beego dashboard I stumbled on a tiny sorting requirement that refused to cooeprate. The goal sounded trivial: given an array that mixes one-, two- and three-digit integers, keep the global ascending order but push every two-digit value to the tail.
const sample = [1, 8, 3, 11, 100, 15, 201];
// expected: [1, 3, 8, 100, 201, 11, 15]
Naïvely I wrote a comparator, ran Array.prototype.sort, and watched the result explode. Thirty minutes later I finally understood why.
The contract of the comparator
Per ECMA-262, compare(x, y) must return
- a negative number →
xprecedesy - zero → relative order is left to the engine (do not rely on stability)
- a positive number →
yprecedesx
Crucially, the engine may envoke the comparator with x and y in any order; the arguments are not guaranteed to reflect their original indices.
Partitioning the problem
We need three mutually exclusive cases:
- Both numbers are two-digit → ordinary ascending order.
- Exact one number is two-digit → the two-digit one must lose.
- Neither is two-digit → ordinary ascending order.
Implementation
function isTwoDigit(n) {
return n >= 10 && n <= 99;
}
function customOrder(left, right) {
const lTwo = isTwoDigit(left);
const rTwo = isTwoDigit(right);
// Case 1
if (lTwo && rTwo) return left - right;
// Case 2
if (lTwo) return 1; // left is two-digit → move right
if (rTwo) return -1; // right is two-digit → keep left
// Case 3
return left - right;
}
const data = [1, 8, 3, 11, 100, 15, 201];
data.sort(customOrder);
console.log(data); // [1, 3, 8, 100, 201, 11, 15]
The key insight: the comparator never swaps elements in place; it merely tells the engine which value should appear first in the final permutation. The actual sorting algorithm (TimSort in V8, for example) runs behind the scenes and may invoke the comparator hundreds of times for modest arrays.