Split in half until pieces are single items, then merge sorted halves back together — guaranteed O(n log n).
Merge sort is the textbook divide-and-conquer sort. Split the list in half, sort each half (by splitting *it* in half, recursively), then merge the two sorted halves into one. A single-element list is already sorted, so the recursion bottoms out cleanly. Its great virtue: it runs in O(n log n) on every input, best or worst.
The clever step is the merge, and it's almost trivial: with two already-sorted halves, the smallest remaining value is always at the front of one of them. Keep a pointer into each half, compare the two fronts, take the smaller, and advance that pointer. Repeat until both are drained.
function mergeSort(arr) {
if (arr.length <= 1) return arr;
const mid = arr.length >> 1;
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
const out = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) out.push(left[i++]); // <= keeps it stable
else out.push(right[j++]);
}
return out.concat(left.slice(i), right.slice(j));
}log n levels to reach single elements. Every level merges a total of n items, so each level costs O(n) and there are log n of them — O(n log n), with no bad-input case to worry about.O(n) extra space. Using <= (not <) when the fronts tie keeps equal values in left-half order, which is what makes it stable.