Merge Sort
Suppose you have two teams of librarians. Each hands you a stack of books already sorted by call number. You must merge them into one sorted shelf. You would glance at the top book of each stack. Take the smaller, and repeat. You never need to look back. This simple merging trick is applied recursively to smaller and smaller stacks. It is the entire idea behind merge sort. Merge sort is one of the oldest and most influential algorithms in computer science. Sorting sits at the foundation of nearly every data-processing system. Databases order query results. Search engines rank documents. External storage systems sort terabytes of data that cannot fit in memory. Merge sort matters because it guarantees Θ(n log n) performance in the worst case, not just on average. It also matters because it is stable. Equal elements keep their relative order. This matters when sorting by one key after already sorting by another. Its structure also makes it the algorithm of choice whenever data must be merged. This applies from disk, across network shards, or in parallel across cores. This article builds merge sort from the ground up. It uses the Divide-and-Conquer paradigm that should already be familiar. We define the algorithm precisely. We walk through the linear-time merge step that does all the real work. We prove correctness by induction on the recursion. We then derive its running time honestly from the recurrence
connecting to the Recurrences techniques (recursion trees, the Master Theorem) used to solve it. By the end, you will be able to write merge sort from memory. You will explain why it is correct via a loop invariant on the merge routine. You will also use induction on the recursion. You will derive its Θ(n log n) bound rigorously. You will understand its space cost and stability tradeoffs. These are relative to algorithms covered later, such as quicksort and the general Comparison-Sort Lower Bound.
- Merge sort recursively splits an array in half, sorts each half, then merges the two sorted halves in linear time.
- It is a textbook Divide-and-Conquer algorithm: divide, conquer, combine — with all the real work happening in "combine."
- Running time satisfies , solving to in every case (best, average, worst).
- It is stable, deterministic, and immune to adversarial worst-case inputs, unlike quicksort.
- Its main cost is Θ(n) auxiliary space for the merge buffer — it is not in-place like quicksort or heapsort.
- It is the backbone of external sorting, stable multi-key sorting, and standard library sorts for linked structures.
Merge sort turns sorting into merging: two already-sorted halves can be combined into one sorted array in linear time, and recursion guarantees the halves are sorted.
The idea
Merge sort is a comparison-based, stable, divide-and-conquer sorting algorithm. Given an array , it operates in three phases characteristic of divide-and-conquer: 1. Divide: split into two contiguous halves, and . 2. Conquer: recursively sort each half. 3. Combine: merge the two now-sorted halves into a single sorted array. The divide step is trivial — it costs constant time to compute a midpoint index. The conquer step is handled entirely by recursion; there is no cleverness there, just trust in the recursive hypothesis (this trust is exactly what the inductive correctness proof formalizes). All of the algorithm's substance lives in the merge operation: combining two sorted sequences of total length into one sorted sequence using only comparisons and moves, and no more than extra space. A sort is called stable if it preserves the relative order of elements that compare equal. Merge sort achieves stability by a simple tie-breaking rule in the merge step: when the front elements of the two halves are equal, always take the element from the left half first. Because the left half's elements originated earlier in the array, this preserves their original relative order. Stability is not a cosmetic property — it is essential when sorting records by a secondary key after already sorting them by a primary key, a common pattern in databases and spreadsheets.
How it works
The algorithm recurses on index ranges of a single array, using an auxiliary array only during the merge step (allocating it once and reusing it, or allocating per-call — both are common; the pseudocode below allocates a temporary buffer at merge time for clarity, noting the standard optimization of reusing one shared buffer).
Base case: an array of length 0 or 1 is already sorted — there is nothing to do.
Merge step: given two adjacent sorted runs and , produce a single sorted run occupying . This is done by maintaining two pointers, one into each run, always copying the smaller of the two "next" elements into an output buffer, and copying it in on ties from the left run to preserve stability. When one run is exhausted, the remainder of the other run is copied directly — no more comparisons are needed since it is already sorted. ``` // Sorts A[p..r] in place, using recursion and a linear merge. MERGE-SORT(A, p, r): if p >= r: return // base case: 0 or 1 elements, already sorted q = floor((p + r) / 2) MERGE-SORT(A, p, q) // conquer left half MERGE-SORT(A, q + 1, r) // conquer right half MERGE(A, p, q, r) // combine // Merges two adjacent sorted runs A[p..q] and A[q+1..r] into one sorted run. MERGE(A, p, q, r): L = A[p..q] // copy left run R = A[q+1..r] // copy right run i = 0 // index into L j = 0 // index into R k = p // index into A, the write cursor while i < length(L) and j < length(R): if L[i] <= R[j]: // "<=" is what makes the sort stable A[k] = L[i] i = i + 1 else: A[k] = R[j] j = j + 1 k = k + 1 while i < length(L): // copy any remaining left elements A[k] = L[i] i = i + 1 k = k + 1 while j < length(R): // copy any remaining right elements A[k] = R[j] j = j + 1 k = k + 1 ``` The top-level call is `MERGE-SORT(A, 1, n)`. Note the two crucial details that a correct implementation must get right: the base case condition `p >= r` (not `p == r`, since recursive calls can produce an empty range only if misused, but more importantly the comparison must stop recursing on singletons), and the tie-break `L[i] <= R[j]` rather than `<`, which is precisely what guarantees stability. The figure below shows the recursive splitting ("divide") followed by the bottom-up merging ("combine") for an eight-element array.
Complexity & correctness
Correctness (proof by strong induction on subarray length). We claim `MERGE-SORT(A, p, r)` correctly sorts into non-decreasing order, for every valid pair . Base case: if , the subarray has at most one element, which is trivially sorted. No work is done, correctly. Inductive step: let and assume, as the inductive hypothesis, that `MERGE-SORT` correctly sorts every subarray strictly shorter than . The call computes so that both (length ) and (length ) are strictly shorter than . By the inductive hypothesis, the two recursive calls leave these subarrays sorted. It remains to show that `MERGE` correctly combines two sorted adjacent runs into one sorted run.
Merge correctness (loop invariant): consider the main `while` loop of `MERGE`, with indices . The invariant is: > At the start of each iteration, consists of the smallest elements of (as a multiset), placed in sorted order, and are the smallest not-yet-copied elements of each run respectively. Initialization: before the first iteration, , ; is empty, vacuously satisfying the invariant. Maintenance: at each step, since and are individually sorted, the smallest element not yet placed must be either or — no earlier element in either run remains unplaced, and no later element in either run can be smaller than its own run's front. The algorithm compares and and copies the smaller (or on ties) into , extending the sorted prefix by one correctly placed element, and increments the corresponding pointer and . Termination: the loop ends when or . At that point every remaining element in the other run is, by the sortedness of that run, at least as large as everything already placed, so copying the rest directly (the two cleanup loops) preserves sortedness. When both cleanup loops finish, contains all elements of and in sorted order. This establishes the inductive step, completing the induction and proving `MERGE-SORT(A, 1, n)` sorts any array of length .
Stability. The tie-breaking rule `L[i] <= R[j]` always prefers the left run on equal keys. Since the left run's elements were located at smaller original indices than the right run's, and this rule is applied consistently at every level of recursion, an easy induction on recursion depth shows equal elements never have their relative order reversed.
Time complexity. `MERGE(A, p, q, r)` does work where : copying into is , and the merge loop performs at most comparisons and exactly writes total (each iteration of every loop writes one element, and there are writes total across all three loops). So merging is in the best, average, and worst case — merging never gets to skip work, unlike some other combine steps. Let be the worst-case running time of `MERGE-SORT` on an array of size . Then:
Assuming is a power of two for a clean derivation (the general case adds only lower-order rounding terms), this is . Unrolling via a recursion tree: at recursion depth , there are subproblems each of size , each contributing merge work, so each full level costs total. The recursion has depth (halving until reaching size 1), giving
Equivalently, by the Master Theorem with , , : since , we are in the "balanced" case, giving directly — the same recurrence machinery introduced when studying Recurrences in general. Crucially, unlike quicksort, this bound holds in the worst case, not merely on average, because the split is always exactly balanced by array position, regardless of the input's values.
Space complexity. The standard implementation uses auxiliary space for the merge buffers, plus stack space for recursion. Merge sort is not in-place: it is impossible to merge two sorted halves of a shared array using only extra space while keeping the merge linear-time — in-place linear merging exists but is complex and typically not worth its constant-factor overhead in practice. This Θ(n) extra memory is the primary tradeoff against algorithms like quicksort or heapsort.
Honest tradeoffs summary:
| Property | Merge Sort |
|---|---|
| Worst-case time | Θ(n log n) — guaranteed |
| Best-case time | Θ(n log n) — merging always costs Θ(n) per level |
| Extra space | Θ(n), not in-place |
| Stable | Yes |
| Adaptive to nearly-sorted input | Not without extra engineering (e.g., natural merge sort) |
| Good for linked lists / external memory | Yes — sequential access pattern, no random access needed |
Worked example
Trace `MERGE-SORT` on (indices ).
Divide phase (recursion splits down to singletons, as shown in the figure): the array splits into and , each splitting again into , , , , each splitting into singletons.
Merge phase, level by level:
| Merge call | Left run | Right run | Comparisons made | Result |
|---|---|---|---|---|
| merge(5, 2) | [5] | [2] | 5 ≤ 2? No → take 2; then take 5 | [2, 5] |
| merge(4, 6) | [4] | [6] | 4 ≤ 6? Yes → take 4; then take 6 | [4, 6] |
| merge(1, 3) | [1] | [3] | 1 ≤ 3? Yes → take 1; then take 3 | [1, 3] |
| merge(2, 6) | [2] | [6] | 2 ≤ 6? Yes → take 2; then take 6 | [2, 6] |
| merge([2,5],[4,6]) | [2,5] | [4,6] | 2≤4→2; 5≤4?No→4; 5≤6?Yes→5; remainder→6 | [2, 4, 5, 6] |
| merge([1,3],[2,6]) | [1,3] | [2,6] | 1≤2→1; 3≤2?No→2; 3≤6?Yes→3; remainder→6 | [1, 2, 3, 6] |
| merge([2,4,5,6],[1,2,3,6]) | [2,4,5,6] | [1,2,3,6] | see below | [1, 2, 2, 3, 4, 5, 6, 6] |
The final merge is worth expanding to show the tie-break rule in action, since both runs contain a `2` and a `6`:
| Step | Comparison | Written | New i, j | ||
|---|---|---|---|---|---|
| 1 | 2 | 1 | 2 ≤ 1? No | 1 | i=0, j=1 |
| 2 | 2 | 2 | 2 ≤ 2? Yes (tie → left wins) | 2 (from L) | i=1, j=1 |
| 3 | 4 | 2 | 4 ≤ 2? No | 2 (from R) | i=1, j=2 |
| 4 | 4 | 3 | 4 ≤ 3? No | 3 | i=1, j=3 |
| 5 | 4 | 6 | 4 ≤ 6? Yes | 4 | i=2, j=3 |
| 6 | 5 | 6 | 5 ≤ 6? Yes | 5 | i=3, j=3 |
| 7 | 6 | 6 | 6 ≤ 6? Yes (tie → left wins) | 6 (from L) | i=4, j=3 (L exhausted) |
| 8 | — | 6 | L exhausted, copy remainder of R | 6 (from R) | j=4 |
Final sorted array: . Note step 2 and step 7 both show the tie-break rule choosing the left run's element first — this is exactly the mechanism that guarantees stability when the two equal `2`s or `6`s originated from records that should retain their relative input order.
Common pitfalls & traps
| Pitfall | Why it breaks the algorithm |
|---|---|
| Computing midpoint as `(p + r) / 2` in a language with fixed-width integers | Can overflow for very large arrays; prefer `p + (r - p) / 2` |
| Using strict `<` instead of `<=` in the merge comparison | Silently destroys stability without causing a visible sorting error |
| Forgetting the two cleanup loops after the main merge loop | Drops the remaining tail of whichever run wasn't exhausted, corrupting the output |
| Off-by-one in base case (`p == r` instead of `p >= r`) | Fails to terminate or mis-handles the case `p > r`, which can arise from careless index arithmetic |
| Re-allocating a fresh temporary array on every merge call | Correct but wasteful — Θ(n) extra allocations per level instead of one shared reusable buffer, hurting constant factors |
| Assuming merge sort is in-place | It is not; naively porting code that assumes O(1) auxiliary space will run out of memory or silently overwrite data on large inputs |
Pioneers & history
Origins · 1 minJohn von Neumann and how this was discovered
Von Neumann is credited with one of the earliest documented descriptions of merge sort, in a 1945 report analyzing sorting procedures suitable for the newly emerging stored-program electronic computer (the EDVAC). He recognized that merging two sorted sequences could be done efficiently and that this could be applied recursively — an early instance of the divide-and-conquer principle later formalized and named across many algorithms.
Merge sort's early tie to tape-based and punch-card sorting machines is not incidental: merging is naturally suited to devices that can only read sequentially, one reason it dominated "external sorting" long before modern RAM-resident sorting became cheap.
Where this matters
Merge sort's guarantees make it the default choice whenever predictability and stability outweigh raw average-case speed or memory frugality. Concretely:
- External sorting: when data is too large to fit in memory (database sort-merge joins, sorting log files spanning many disk blocks), data is split into chunks that fit in memory, sorted, and then merged sequentially from disk — exactly the merge-sort pattern, since merging needs only sequential access, not random access.
- Standard library sorts for objects: Java's `Arrays.sort` for object arrays, and Python's Timsort (used in `list.sort` and `sorted`), rely on merge-sort-family algorithms specifically because sorting user-defined objects by a key must be stable to behave predictably under repeated, multi-key sorts.
- Linked lists: merge sort adapts naturally to linked lists, needing only pointer rearrangement and no random-access indexing, unlike quicksort or heapsort, which lean on array indexing or in-place swaps.
- Parallel and distributed sorting: because the two recursive halves are entirely independent, merge sort parallelizes cleanly across cores or machines (e.g., MapReduce-style shuffle-and-sort phases), with only the merge step requiring coordination.
- Inversion counting and related problems: the merge step naturally counts how many pairs are "out of order" with a small modification, a classic application taught alongside merge sort itself.
Merge sort also serves as the canonical example when introducing the Comparison-Sort Lower Bound: since it achieves Θ(n log n) comparisons, and no comparison-based sort can beat Ω(n log n) in the worst case (an information-theoretic bound from counting the possible orderings), merge sort is asymptotically optimal among comparison sorts — a benchmark against which algorithms like Quicksort are then compared on other axes, such as space usage and average-case constants.
Ask the tutor: How would you modify the merge step to count the number of inversions in an array, and why does this run in the same Θ(n log n) time as the sort itself?
Is merge sort always better than quicksort?
No. Merge sort guarantees Θ(n log n) worst-case time and is stable, but it needs Θ(n) extra space and typically has larger constant factors than a well-implemented quicksort due to the overhead of copying into buffers. Quicksort sorts in place (Θ(log n) expected stack space) and tends to be faster in practice on random data due to better cache locality and lower per-element overhead, but its worst case is Θ(n²) without randomization or careful pivot selection. The right choice depends on whether worst-case guarantees, stability, or average-case speed and memory footprint matter most for the application.
Can merge sort be made to run in-place, using O(1) extra space?
Truly in-place linear-time merging is theoretically possible but practically painful: known in-place merge algorithms either sacrifice linear time, sacrifice stability, or introduce large constant-factor overheads from intricate block-rotation techniques. In practice, "in-place" merge sort variants used in production systems typically still use a small amount of extra space or accept a higher constant factor. The standard, widely taught, and widely deployed version of merge sort simply uses Θ(n) auxiliary space, trading memory for simplicity, speed, and a clean correctness proof.
Why is merge sort's worst case the same as its best case, unlike quicksort's?
Merge sort always splits the array by position — exactly in half by index — regardless of the values stored there, so the recursion tree's shape never depends on the input. The merge step also always performs Θ(n) work, since it must move every element into the output buffer regardless of how sorted the inputs already are. Quicksort, in contrast, splits by value around a pivot, so an adversarial or unlucky pivot choice can produce wildly unbalanced partitions, driving its worst case to Θ(n²) even though its average case is Θ(n log n).
What does it mean for a sort to be "stable," and why does it matter?
A sort is stable if elements that compare equal under the sort key retain their original relative order in the output. This matters whenever you sort by one key and later need to sort by another while preserving the first ordering as a tiebreaker — for example, sorting a spreadsheet first by last name, then stably by department, so that within each department names remain alphabetical. Unstable sorts can silently scramble such secondary ordering, producing outputs that are technically "sorted" by the final key but wrong for any application relying on the earlier ordering.
How does merge sort relate to the comparison-sort lower bound?
Any sorting algorithm that only compares elements pairwise must, in the worst case, perform at least Ω(n log n) comparisons — this follows from an information-theoretic argument counting the n! possible input orderings and noting a binary decision tree of comparisons needs depth at least log₂(n!) = Ω(n log n) to distinguish them all. Merge sort's Θ(n log n) worst-case bound matches this lower bound exactly, meaning merge sort is asymptotically optimal among comparison-based sorts — no comparison sort can do asymptotically better in the worst case.
How is merge sort adapted for linked lists versus arrays?
On a linked list, merge sort avoids its main weakness on arrays: it needs no extra Θ(n) buffer, because merging two sorted linked lists can be done by rewiring `next` pointers in place, producing a merged list using only O(1) extra space (aside from recursion overhead). Splitting a linked list in half, however, requires a full traversal to find the midpoint (no random-access indexing), typically done with a slow/fast pointer technique. This makes merge sort one of the few sorts that is genuinely preferable to quicksort on linked lists, since quicksort's in-place partitioning trick relies on array indexing that linked lists lack.
What role does merge sort play in real-world sort implementations like Timsort?
Timsort, used by Python and by Java for object arrays, is fundamentally a merge sort augmented with adaptivity: it first scans the input for existing sorted "runs" (ascending or descending stretches), extends short runs up to a minimum length using insertion sort, and then merges runs together using merge-sort's linear merge, with careful heuristics (galloping mode) to speed up merges when one run consistently dominates. This hybrid keeps merge sort's Θ(n log n) worst case and stability while achieving close to linear time on partially sorted real-world data, which is extremely common in practice (e.g., re-sorting after small updates).