AlgoMaster Logo

Merge Sort

Last Updated: March 31, 2026

Ashish

Ashish Pratap Singh

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

The key idea lies in the merge step, where two sorted arrays are combined efficiently to produce a fully sorted result. This guarantees a consistent time complexity of O(n log n), regardless of the input.

Merge Sort is stable, predictable, and widely used in practice, especially when working with linked lists or large datasets that do not fit entirely in memory.

In this chapter, you will learn how Merge Sort works step by step, how to implement it, and why it is one of the most important sorting algorithms to understand.

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. Then the magic happens during the merge step, where two sorted subarrays are combined 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?

This is what makes merge sort special. Unlike quicksort, which can degrade to O(n^2) on bad inputs, merge sort always splits the array exactly in half. That means:

  • The recursion tree always has exactly log n levels (since we halve the array each time).
  • At each level, every element participates in exactly one merge operation, so the total work per level is O(n).
  • Multiplying these together: O(n) work per level x log n levels = O(n log n) total.

There is no best case or worst case. The input order does not matter. Merge sort always does the same amount of work because the splitting and merging structure is fixed.

How It Works

Let us break down the algorithm into its two core pieces: the recursive splitting logic and the merge procedure.

The Recursive Structure

The high-level logic is straightforward:

  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.

The key insight is that by the time we call merge(), both halves are already sorted. The left half arr[left..mid] is sorted, and the right half arr[mid+1..right] is sorted. Our only job is to combine them.

The Merge Procedure

Merging two sorted arrays is where the real work happens. The idea 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, we compare the front elements of both halves and pick the smaller one. Because both halves are sorted, this guarantees the merged result is also sorted.

Code Implementation

Notice that the implementations use <= (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, preserving their original relative order.

Example Walkthrough

Let us trace through merge sort step by step with the array [38, 27, 43, 3, 9, 82, 10]. Following the recursion carefully helps you understand exactly when splits and merges happen.

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.

Phase 2: Merge (Bottom-Up)

Now we merge back up, combining sorted subarrays at each level.

Merge [27] and [43]:

Merge [38] and [27, 43]:

Merge [3] and [9]:

Merge [82] and [10]:

Merge [3, 9] and [10, 82]:

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

This is the most interesting merge because both halves have multiple elements and the pointers alternate between sides.

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

Notice how the merge step never needs to "go back" or re-examine 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.

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's trade-off is clear: guaranteed performance and stability at the cost of extra memory. Quick sort is typically faster in practice 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. When you cannot afford worst-case degradation, merge sort is the safe choice. This matters in real-time systems or when processing untrusted input that could be adversarially crafted.
  • Sorting linked lists. Merge sort is ideal for linked lists because the merge step can be done in O(1) extra space (by re-linking nodes instead of copying to temporary arrays). You also do not need random access for splitting, since you can find the midpoint with the fast-slow pointer technique.
  • External sorting. When data is too large to fit in memory, you 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. If you need equal elements to keep their original relative order (for example, sorting records by one field while preserving a previous sort order on another field), merge sort naturally provides this.
  • Parallelism. The two recursive halves are independent of each other, making merge sort easy to parallelize. You can sort each half on a different thread or even a different machine.

Not Ideal For

  • Memory-constrained environments. The O(n) extra space is a real cost. If you are sorting millions of elements in a memory-tight environment, quick sort or heap sort might be 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). This is why optimized implementations switch to insertion sort for small subarrays.
  • Nearly sorted data. Algorithms like insertion sort or Timsort can exploit existing order for O(n) performance on nearly-sorted input. Merge sort does the same amount of work regardless.