Merge Sort

Sort in O(n log n) by splitting the array in half, sorting each half, and merging two sorted lists. Traced step by step, with JS and Python code.

16 minLesson 2 of 7
On this page
  1. The idea: split, sort, merge
  2. The merge, traced
  3. The code
  4. Why it is O(n log n)
  5. Stable, if you write it right
  6. Common mistakes
  7. Check yourself
  8. What to practise next

Two teaching assistants each sort half of a stack of exam papers by score. They hand you their two sorted piles, face up. To make one sorted pile, you do not need to sort anything again. Look at the top paper of each pile, take the lower score, and repeat. Each paper costs you one glance.

That is the merge step, and it is the whole trick behind merge sort. Merging two sorted lists is cheap. So if you can get two sorted halves, you can get a sorted whole. And the way to get sorted halves is to use the same trick on each half.

The idea: split, sort, merge

Merge sort is recursive:

  1. Base case. A list of 0 or 1 items is already sorted. Return it.
  2. Split the list into a left half and a right half.
  3. Sort each half with merge sort.
  4. Merge the two sorted halves into one sorted list.

Step 3 can look like cheating, because it calls the function you are still writing. Recursion makes it work. Every call gets a smaller list, so eventually every call hits the base case. All the real work happens on the way back up, in the merges. This pattern of splitting a problem, solving the parts and combining them is called divide and conquer.

split no comparisons merge 8 items per row 3 rows = log₂ 8 6 2 9 5 1 8 3 7 6 2 9 5 1 8 3 7 6 2 9 5 1 8 3 7 6 2 9 5 1 8 3 7 2 6 5 9 1 8 3 7 2 5 6 9 1 3 7 8 1 2 3 5 6 7 8 9
Merge sort on 8 items. Splitting needs no comparisons. Each merge row on the way back up touches all 8 items, and there are 3 merge rows.

The merge, traced

Here is the last merge from the figure. The left half [2, 5, 6, 9] and the right half [1, 3, 7, 8] are both sorted. Pointer i marks the next item in the left half, j the next item in the right half. This is the "two lists" shape of two pointers.

Merging [2, 5, 6, 9] and [1, 3, 7, 8]Use the arrows or ← → keys
i = 0, j = 0, out = []while i < len(left) and j < len(right):  if left[i] <= right[j]:    out.append(left[i]); i = i + 1  else:    out.append(right[j]); j = j + 1append what is left of left, then of right
  1. left
    02i
    15
    26
    39
    right
    01j
    13
    27
    38
    out
    0
    1
    2
    3
    4
    5
    6
    7
    comparisons
    0

    1Both halves are sorted, so the smallest item overall must be at the front of one of them: 2 or 1.

  2. left
    02i
    15
    26
    39
    right
    01
    13j
    27
    38
    out
    01
    1
    2
    3
    4
    5
    6
    7
    comparisons
    1

    21 is smaller than 2. Take 1 and move j.

  3. left
    02
    15i
    26
    39
    right
    01
    13j
    27
    38
    out
    01
    12
    2
    3
    4
    5
    6
    7
    comparisons
    2

    32 vs 3: take 2 from the left and move i.

  4. left
    02
    15i
    26
    39
    right
    01
    13
    27j
    38
    out
    01
    12
    23
    3
    4
    5
    6
    7
    comparisons
    3

    45 vs 3: take 3.

  5. left
    02
    15
    26i
    39
    right
    01
    13
    27j
    38
    out
    01
    12
    23
    35
    4
    5
    6
    7
    comparisons
    4

    55 vs 7: take 5.

  6. left
    02
    15
    26
    39i
    right
    01
    13
    27j
    38
    out
    01
    12
    23
    35
    46
    5
    6
    7
    comparisons
    5

    66 vs 7: take 6.

  7. left
    02
    15
    26
    39i
    right
    01
    13
    27
    38j
    out
    01
    12
    23
    35
    46
    57
    6
    7
    comparisons
    6

    79 vs 7: take 7.

  8. left
    02
    15
    26
    39i
    right
    01
    13
    27
    38
    j
    out
    01
    12
    23
    35
    46
    57
    68
    7
    comparisons
    7

    89 vs 8: take 8. The right half is now empty, so the loop stops.

  9. left
    02
    15
    26
    39
    i
    right
    01
    13
    27
    38
    j
    out
    01
    12
    23
    35
    46
    57
    68
    79
    comparisons
    7

    9Whatever is left in the other half is already sorted and bigger than everything in out. Copy it over without comparing: 9.

  10. out
    01
    12
    23
    35
    46
    57
    68
    79
    comparisons
    7
    items
    8

    108 items merged with 7 comparisons. A merge of n items never needs more than n − 1, because each comparison places one item for good.

1 / 10

The code

The code is the four steps from above plus the merge you just traced. This version returns a new sorted list and leaves the input alone.

function mergeSort(a) {
  if (a.length <= 1) return a; // base case
  const mid = Math.floor(a.length / 2);
  const left = mergeSort(a.slice(0, mid));
  const right = mergeSort(a.slice(mid));
  return merge(left, right);
}
 
function merge(left, right) {
  const out = [];
  let i = 0, j = 0;
  while (i < left.length && j < right.length) {
    // <= takes from the left on ties: that keeps the sort stable
    if (left[i] <= right[j]) out.push(left[i++]);
    else out.push(right[j++]);
  }
  // one half is empty; the rest of the other is already sorted
  while (i < left.length) out.push(left[i++]);
  while (j < right.length) out.push(right[j++]);
  return out;
}
 
mergeSort([6, 2, 9, 5, 1, 8, 3, 7]); // [1, 2, 3, 5, 6, 7, 8, 9]
def merge_sort(a):
    if len(a) <= 1:  # base case
        return a
    mid = len(a) // 2
    left = merge_sort(a[:mid])
    right = merge_sort(a[mid:])
    return merge(left, right)
 
def merge(left, right):
    out = []
    i = j = 0
    while i < len(left) and j < len(right):
        # <= takes from the left on ties: that keeps the sort stable
        if left[i] <= right[j]:
            out.append(left[i])
            i += 1
        else:
            out.append(right[j])
            j += 1
    # one half is empty; the rest of the other is already sorted
    out.extend(left[i:])
    out.extend(right[j:])
    return out
 
merge_sort([6, 2, 9, 5, 1, 8, 3, 7])  # [1, 2, 3, 5, 6, 7, 8, 9]

Why it is O(n log n)

Look at the figure again, row by row. Every merge row handles all n items once, so each row costs O(n). How many merge rows are there? Each split halves the size, and you can only halve n about log₂ n times before reaching single items. For 8 items that is 3 rows; for a million items, about 20.

So the total is n items × log n rows = O(n log n). For a million items, that is about 20 million steps, compared with about 500 billion for the simple sorts. And unlike insertion sort, this does not depend on the input: sorted, reversed or random, merge sort does the same splits and roughly the same work.

The price is memory. The merge writes into a new list, so merge sort needs O(n) extra space. The recursion itself only goes log n calls deep.

TimeO(n log n)
SpaceO(n)

log n levels of merging, O(n) work per level, in every case. The merge needs a second list. Stable.

Stable, if you write it right

When left[i] and right[j] are equal, the code takes the left one. Items in the left half came first in the original list, so equal items keep their original order. That makes merge sort stable, which is why it is the usual choice for sorting records by several fields.

Common mistakes

Check yourself

Quick check

You merge [1, 2, 3] with [10, 20, 30]. How many comparisons does the merge loop make?

Merge sort on a list that is already sorted. What is its time complexity?

How much extra memory does the merge sort above need, ignoring the recursion?

What to practise next

Trace the full sort of [4, 1, 3, 2] by hand: two merges of pairs, then one merge of halves. Then read Quicksort, which flips the plan around: it does its work while splitting instead of while merging, and needs no extra list.