AlgoMaster Logo

Merge Sort

High Priority11 min readUpdated July 10, 2026
Listen to this chapter
Unlock Audio

Merge sort is a classic divide-and-conquer algorithm. It repeatedly splits the array into halves until each part contains a single element, then merges those parts back together in sorted order.

Its time complexity is a consistent O(n log n) regardless of the input. Merge sort is also stable and widely used in practice, particularly for linked lists and large datasets that do not fit entirely in memory.

Loading simulation...

This chapter covers how merge sort works, how to implement it, and why it remains a foundational sorting algorithm.

What Is Merge Sort?

Merge sort is a divide-and-conquer algorithm. It breaks a problem into smaller subproblems, solves each one independently, and then combines the results. For sorting, that means three steps:

  1. Divide the array into two halves.
  2. Conquer each half by recursively sorting it.
  3. Combine the two sorted halves by merging them into one sorted array.

The recursion bottoms out when a subarray has zero or one element, since a single element is already sorted. The merge step then combines two sorted subarrays into one.

Here is what the full split-and-merge process looks like for a small array:

The top half of the diagram shows the divide phase, where we keep splitting until every subarray has one element. The bottom half shows the combine phase, where sorted subarrays merge back together, growing larger at each level until the full array is sorted.

Why O(n log n) in All Cases?

Unlike quicksort, which can degrade to O(n^2) on bad inputs, merge sort always splits the array in half (within one element for odd sizes). That means:

  • The recursion tree always has about log n levels, since halving the array repeatedly takes log₂ n steps to reach single elements.
  • At each level, every element participates in exactly one merge operation, so the total work across one level is O(n).
  • Multiplying these together gives O(n) work per level times log n levels, or O(n log n) total.

The input order does not change this. Merge sort does the same amount of work on sorted, reversed, or random input because the splitting and merging structure is fixed.

How It Works

The algorithm has two core pieces: the recursive splitting logic and the merge procedure.

The Recursive Structure

The high-level logic has five steps:

  1. If the array has 0 or 1 elements, return (base case).
  2. Find the midpoint: mid = left + (right - left) / 2.
  3. Recursively sort the left half: mergeSort(arr, left, mid).
  4. Recursively sort the right half: mergeSort(arr, mid + 1, right).
  5. Merge the two sorted halves back into the original array.

By the time merge() is called, both halves are already sorted: arr[left..mid] on the left, arr[mid+1..right] on the right. The merge step's only job is to combine them into one sorted range.

The Merge Procedure

Merging two sorted arrays uses a two-pointer technique:

  1. Create a temporary array to hold the merged result.
  2. Place one pointer at the start of the left half and another at the start of the right half.
  3. Compare the elements at both pointers. Copy the smaller one into the temporary array and advance that pointer.
  4. Repeat until one half is exhausted.
  5. Copy any remaining elements from the non-exhausted half.
  6. Copy the temporary array back into the original array.

This merge step is what gives merge sort its name, and it runs in O(n) time because each element is visited exactly once.

At each step of the merge, the algorithm compares the front elements of both halves and picks the smaller one. Because both halves are sorted, the merged result is also sorted.

Code Implementation

The implementation uses <= (not <) when comparing elements from the left and right halves. This is what makes merge sort stable: equal elements from the left half always come before equal elements from the right half, which preserves their original relative order.

Example Walkthrough

Trace merge sort on the array [38, 27, 43, 3, 9, 82, 10], following the recursion through both splits and merges. The code uses mid = left + (right - left) / 2 with right inclusive, so a range of size 7 splits into 4 + 3 elements.

Phase 1: Divide

The algorithm splits the array recursively until every subarray has at most one element.

At Level 3, every subarray is a single element. Single elements are sorted by definition, so the recursion starts unwinding. The [10] subarray at Level 2 is already a single element and waits at this level until its sibling [9, 82] finishes.

Phase 2: Merge (Bottom-Up)

Merging proceeds upward, combining sorted subarrays at each level.

Merge [38] and [27]:

Merge [43] and [3]:

Merge [27, 38] and [3, 43]:

Merge [9] and [82]:

Merge [9, 82] and [10]:

Final Merge [3, 27, 38, 43] and [9, 10, 82]:

Both halves have multiple elements here, so the pointers alternate between sides several times before either half is exhausted.

The final sorted array is [3, 9, 10, 27, 38, 43, 82].

The merge step never goes back or re-examines elements. Each element is compared and placed exactly once per merge, which is why each merge level takes O(n) total work.

Complexity Analysis

CaseTime ComplexityExplanation
BestO(n log n)Always splits in half, always merges. No shortcuts.
AverageO(n log n)Same structure regardless of input order.
WorstO(n log n)Same. The split-merge structure is input-independent.
SpaceO(n)Temporary arrays used during merge.
StableYesEqual elements maintain their relative order.

Why O(n) Space?

Each merge step creates temporary arrays to hold the two halves being merged. At the top level, the temporary arrays hold all n elements. While deeper recursion levels use smaller arrays, those arrays are freed before the top-level merge begins (since recursion unwinds bottom-up). So the peak additional memory usage is O(n).

The call stack also uses O(log n) space due to the recursion depth, but this is dominated by the O(n) temporary arrays.

Stability

Merge sort is stable. The merge step uses <= (not <) when comparing the front elements of the two halves, so an element from the left half is taken first whenever it equals an element on the right. Since the left half originally appeared earlier in the input, equal elements preserve their original order.

This makes merge sort a common choice when sorting records by multiple fields: a stable sort on the secondary key followed by a stable sort on the primary key produces a result correctly ordered by primary, with ties broken by secondary.

Comparison with Other O(n log n) Sorts

PropertyMerge SortQuick SortHeap Sort
Worst-case timeO(n log n)O(n^2)O(n log n)
Average timeO(n log n)O(n log n)O(n log n)
SpaceO(n)O(log n)O(1)
StableYesNoNo
Cache-friendlyModerateExcellentPoor
In-placeNoYesYes

Merge sort trades extra memory for guaranteed performance and stability. Quick sort is typically faster due to better cache locality, but it cannot guarantee O(n log n) without careful pivot selection.

When to Use Merge Sort

Good For

  • Guaranteed O(n log n) performance. Merge sort matters in real-time systems or when processing untrusted input that could be adversarially crafted, because worst-case degradation cannot occur.
  • Sorting linked lists. Merge sort fits linked lists well because the merge step can be done in O(1) extra space (by re-linking nodes instead of copying to temporary arrays). Splitting does not need random access either, since the midpoint can be found with the fast-slow pointer technique.
  • External sorting. When data is too large to fit in memory, the standard approach is to split it into chunks that fit in RAM, sort each chunk with any algorithm, and then merge the sorted chunks. This multi-way merge is the foundation of how databases and big data systems sort terabytes of data.
  • Stability is required. Sorting records by one field while preserving a previous sort order on another field needs a stable algorithm, and merge sort provides this naturally.
  • Parallelism. The two recursive halves are independent of each other, which makes merge sort easy to parallelize. Each half can run on a different thread or even a different machine.

Not Ideal For

  • Memory-constrained environments. The O(n) extra space is a real cost. For millions of elements in a memory-tight environment, quick sort or heap sort are better choices.
  • Small arrays. The overhead of recursion and temporary array creation makes merge sort slower than simpler algorithms like insertion sort for small arrays (typically n < 20-30), which is why optimized implementations switch to insertion sort for small subarrays.
  • Nearly sorted data. Algorithms like insertion sort or Timsort exploit existing order for O(n) performance on nearly-sorted input. Merge sort does the same amount of work regardless.

Quiz

Merge Sort Quiz

10 quizzes