Divide and conquer
Merge sort splits the array in half, recursively sorts each half, then merges the two sorted halves into one sorted result. The key insight is that merging two sorted arrays is O(n) — you walk both simultaneously, always taking the smaller front element.
Why O(n log n)
The recursion splits the array in half each time, producing log₂(n) levels. At each level, the total work across all merges is O(n) — every element is visited once during merging. Total: O(n) work × O(log n) levels = O(n log n). This holds in best, average, and worst case — the split is always even.
The O(n) space cost
Merge sort needs auxiliary memory for the merge step — you can't merge two sorted
subarrays in-place without O(n²) work. The tmp buffer above allocates
and frees on every merge call, which is fine for correctness but expensive in practice.
Real implementations allocate one auxiliary buffer of size n upfront and reuse it.
<= when comparing, which means equal elements from the
left half appear before equal elements from the right half — their original order is preserved.
This is why databases and spreadsheets often use merge sort variants.
Merge sort guarantees O(n log n) by splitting in half and merging in linear time — at the cost of O(n) extra space.