DEV Community

Shankar L
Shankar L

Posted on

Merge Sort : Sorting in O(n log n) Using Divide and Conquer

Why should you care?

Sorting is one of the most common problems in programming.

You may need to sort:

  • Student marks
  • Product prices
  • Names
  • Search results
  • Timestamps
  • Database records
  • Large datasets

A simple sorting algorithm might work well for a small array, but performance becomes important when the input grows.

Merge Sort can sort n elements in:

O(n log n)
Enter fullscreen mode Exit fullscreen mode

More importantly, Merge Sort introduces a powerful problem-solving technique:

Divide the problem into smaller problems, solve them, and combine the results.

This technique is called Divide and Conquer and appears throughout computer science.


The Problem

Suppose we have:

[38, 27, 43, 3, 9, 82, 10]
Enter fullscreen mode Exit fullscreen mode

We want:

[3, 9, 10, 27, 38, 43, 82]
Enter fullscreen mode Exit fullscreen mode

A straightforward approach is to repeatedly find the smallest element and place it in the correct position.

But as the input grows, some sorting algorithms become very slow.

For example, a quadratic algorithm has:

O(n²)
Enter fullscreen mode Exit fullscreen mode

complexity.

Merge Sort improves this to:

O(n log n)
Enter fullscreen mode Exit fullscreen mode

The key idea is that instead of trying to sort the entire array at once, we repeatedly divide it into smaller pieces.


The Concept

Merge Sort follows three major steps:

Divide
  ↓
Conquer
  ↓
Merge
Enter fullscreen mode Exit fullscreen mode

1. Divide

Split the array into two halves.

[38, 27, 43, 3, 9, 82, 10]

          ↓

[38, 27, 43]    [3, 9, 82, 10]
Enter fullscreen mode Exit fullscreen mode

Continue dividing:

[38, 27, 43]
      ↓
[38] [27, 43]
         ↓
      [27] [43]
Enter fullscreen mode Exit fullscreen mode

Eventually every part contains one element.


2. Conquer

A single-element array is already sorted.

[38]
[27]
[43]
Enter fullscreen mode Exit fullscreen mode

Now we begin combining them.


3. Merge

Merge two sorted arrays into one sorted array.

For example:

[27] + [43]
Enter fullscreen mode Exit fullscreen mode

becomes:

[27, 43]
Enter fullscreen mode Exit fullscreen mode

Then:

[38] + [27, 43]
Enter fullscreen mode Exit fullscreen mode

becomes:

[27, 38, 43]
Enter fullscreen mode Exit fullscreen mode

This merging process continues until the entire array is sorted.


Simple Explanation

Imagine you have a pile of 1,000 papers that need to be sorted by number.

Instead of sorting all 1,000 papers at once:

  1. Split them into two piles.
  2. Split each pile again.
  3. Keep splitting until each pile has one paper.
  4. Combine small sorted piles.
  5. Continue combining larger sorted piles.
  6. Eventually you get one completely sorted pile.

The clever part is the merge.

When two groups are already sorted, combining them is easy.

For example:

Group A:
[2, 7, 15]

Group B:
[3, 5, 12]
Enter fullscreen mode Exit fullscreen mode

Compare the front elements:

2 vs 3 → take 2
7 vs 3 → take 3
7 vs 5 → take 5
7 vs 12 → take 7
15 vs 12 → take 12
Enter fullscreen mode Exit fullscreen mode

Finally:

[2, 3, 5, 7, 12, 15]
Enter fullscreen mode Exit fullscreen mode

This is the core operation behind Merge Sort.


Real-world Analogy

Imagine two queues of students where each queue is already sorted by height.

Queue A:
Short → Medium → Tall

Queue B:
Short → Medium → Tall
Enter fullscreen mode Exit fullscreen mode

You don't need to completely reorder either queue.

You simply compare the person at the front of each queue.

Take the shorter person.

Then compare the new front positions.

Repeat until both queues are empty.

That's exactly what the merge step does.

Merge Sort creates many small sorted sequences and then efficiently merges them.


Code Example

Let's implement Merge Sort in Java.

public static void mergeSort(int[] arr, int left, int right) {

    if (left >= right) {
        return;
    }

    int mid = left + (right - left) / 2;

    mergeSort(arr, left, mid);
    mergeSort(arr, mid + 1, right);

    merge(arr, left, mid, right);
}
Enter fullscreen mode Exit fullscreen mode

Now we need the merge operation:

public static void merge(
        int[] arr,
        int left,
        int mid,
        int right) {

    int[] temp = new int[right - left + 1];

    int i = left;
    int j = mid + 1;
    int k = 0;

    while (i <= mid && j <= right) {

        if (arr[i] <= arr[j]) {
            temp[k++] = arr[i++];
        } else {
            temp[k++] = arr[j++];
        }
    }

    while (i <= mid) {
        temp[k++] = arr[i++];
    }

    while (j <= right) {
        temp[k++] = arr[j++];
    }

    for (int x = 0; x < temp.length; x++) {
        arr[left + x] = temp[x];
    }
}
Enter fullscreen mode Exit fullscreen mode

We can use it like this:

public static void main(String[] args) {

    int[] arr = {
        38, 27, 43, 3, 9, 82, 10
    };

    mergeSort(arr, 0, arr.length - 1);

    for (int value : arr) {
        System.out.print(value + " ");
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

3 9 10 27 38 43 82
Enter fullscreen mode Exit fullscreen mode

How the code works

The recursive function first keeps splitting:

[38, 27, 43, 3, 9, 82, 10]
Enter fullscreen mode Exit fullscreen mode

into smaller pieces.

Eventually:

[38] [27] [43] [3] [9] [82] [10]
Enter fullscreen mode Exit fullscreen mode

Then merge() starts combining them.

For example:

[27] + [43]
Enter fullscreen mode Exit fullscreen mode

becomes:

[27, 43]
Enter fullscreen mode Exit fullscreen mode

Then:

[38] + [27, 43]
Enter fullscreen mode Exit fullscreen mode

becomes:

[27, 38, 43]
Enter fullscreen mode Exit fullscreen mode

The same process happens on the other side.

Finally:

[27, 38, 43]
+
[3, 9, 10, 82]
Enter fullscreen mode Exit fullscreen mode

becomes:

[3, 9, 10, 27, 38, 43, 82]
Enter fullscreen mode Exit fullscreen mode

Visualizing Merge Sort

The complete process looks like this:

Starting array:

[38, 27, 43, 3, 9, 82, 10]
Enter fullscreen mode Exit fullscreen mode

Divide

             [38 27 43 3 9 82 10]
                    /       \
             [38 27 43]   [3 9 82 10]
              /    \        /      \
           [38]  [27 43]  [3 9]  [82 10]
                  / \      / \     / \
                [27][43] [3][9] [82][10]
Enter fullscreen mode Exit fullscreen mode

Now every piece is individually sorted.

Merge

[27] + [43]
      ↓
[27 43]

[3] + [9]
      ↓
[3 9]

[82] + [10]
       ↓
[10 82]
Enter fullscreen mode Exit fullscreen mode

Continue:

[38] + [27 43]
        ↓
[27 38 43]

[3 9] + [10 82]
        ↓
[3 9 10 82]
Enter fullscreen mode Exit fullscreen mode

Finally:

[27 38 43]
      +
[3 9 10 82]

        ↓

[3 9 10 27 38 43 82]
Enter fullscreen mode Exit fullscreen mode

Time Complexity

Merge Sort has:

O(n log n)
Enter fullscreen mode Exit fullscreen mode

time complexity.

Why?

There are two important parts.

Number of levels

Every time we divide the array, its size is approximately halved.

Therefore, the number of levels is:

log n
Enter fullscreen mode Exit fullscreen mode

Work at each level

At every level, all elements are processed during merging.

That's:

O(n)
Enter fullscreen mode Exit fullscreen mode

work.

Therefore:

O(n) × O(log n)
Enter fullscreen mode Exit fullscreen mode

gives:

O(n log n)
Enter fullscreen mode Exit fullscreen mode

This applies to the best, average, and worst cases for the standard Merge Sort algorithm.

Case Time
Best O(n log n)
Average O(n log n)
Worst O(n log n)

Space Complexity

The implementation above creates temporary arrays during merging.

Therefore, its auxiliary space complexity is:

O(n)
Enter fullscreen mode Exit fullscreen mode

The recursive calls also require stack space:

O(log n)
Enter fullscreen mode Exit fullscreen mode

But the temporary merge arrays dominate the additional memory usage.

So the typical overall auxiliary space is:

O(n)
Enter fullscreen mode Exit fullscreen mode

This is one of the major trade-offs of Merge Sort:

Excellent time complexity
        ↓
O(n log n)

But

Additional memory required
        ↓
O(n)
Enter fullscreen mode Exit fullscreen mode

Common Mistakes

Mistake 1: Forgetting the base case

Recursive Merge Sort needs to stop when the range contains one element.

if (left >= right) {
    return;
}
Enter fullscreen mode Exit fullscreen mode

Without this condition, the recursion never terminates.


Mistake 2: Incorrectly calculating the middle

Avoid:

int mid = (left + right) / 2;
Enter fullscreen mode Exit fullscreen mode

For large indexes, addition can overflow.

Prefer:

int mid = left + (right - left) / 2;
Enter fullscreen mode Exit fullscreen mode

This is the same safe calculation we saw with Binary Search.


Mistake 3: Forgetting the remaining elements

During merging, one side may become empty first.

For example:

Left:
[2, 5, 8]

Right:
[3]
Enter fullscreen mode Exit fullscreen mode

After selecting:

2
3
Enter fullscreen mode Exit fullscreen mode

the left side still contains:

5, 8
Enter fullscreen mode Exit fullscreen mode

These elements must be copied into the result.

That's why we need:

while (i <= mid) {
    temp[k++] = arr[i++];
}
Enter fullscreen mode Exit fullscreen mode

and:

while (j <= right) {
    temp[k++] = arr[j++];
}
Enter fullscreen mode Exit fullscreen mode

Mistake 4: Thinking the merge operation sorts arbitrary arrays

The merge step assumes that both input portions are already sorted.

For example:

[2, 7, 10]
[1, 5, 9]
Enter fullscreen mode Exit fullscreen mode

can be efficiently merged.

But:

[7, 2, 10]
[9, 1, 5]
Enter fullscreen mode Exit fullscreen mode

cannot simply be merged correctly without first sorting those portions.

That's why Merge Sort works from the bottom up: smaller pieces become sorted before larger pieces are merged.


Advanced Notes

1. Merge Sort is Stable

A sorting algorithm is called stable if equal elements maintain their original relative order.

Consider:

(John, 90)
(Alex, 90)
Enter fullscreen mode Exit fullscreen mode

If sorting by marks, a stable algorithm keeps:

John, 90
Alex, 90
Enter fullscreen mode Exit fullscreen mode

in their original relative order.

Our merge implementation uses:

if (arr[i] <= arr[j])
Enter fullscreen mode Exit fullscreen mode

rather than:

if (arr[i] < arr[j])
Enter fullscreen mode Exit fullscreen mode

This allows the element from the left half to be selected first when values are equal.

Therefore, this implementation is stable.


2. Merge Sort vs Quick Sort

Both are important O(n log n) sorting algorithms.

Feature Merge Sort Quick Sort
Average Time O(n log n) O(n log n)
Worst Time O(n log n) O(n²)
Stable Yes Usually No
Extra Space O(n) Typically O(log n) stack
Main Idea Divide + Merge Divide around Pivot

The biggest difference is the strategy.

Merge Sort divides the array and then performs a carefully controlled merge.

Quick Sort chooses a pivot and partitions the elements around it.


3. Bottom-Up Merge Sort

The implementation we've seen is top-down Merge Sort.

It starts with the entire array:

[entire array]
Enter fullscreen mode Exit fullscreen mode

and recursively divides it.

There is another approach called bottom-up Merge Sort.

It starts with individual elements:

[38] [27] [43] [3] [9] [82] [10]
Enter fullscreen mode Exit fullscreen mode

Then merges pairs:

[27 38] [3 43] [9 82] [10]
Enter fullscreen mode Exit fullscreen mode

Then:

[3 27 38 43] [9 10 82]
Enter fullscreen mode Exit fullscreen mode

And finally:

[3 9 10 27 38 43 82]
Enter fullscreen mode Exit fullscreen mode

It avoids recursion and can be useful in certain implementations.


4. Merge Sort on Linked Lists

Merge Sort is particularly well suited to linked lists.

Why?

Linked lists don't provide efficient random access.

An algorithm that constantly needs:

arr[mid]
Enter fullscreen mode Exit fullscreen mode

is not ideal for a linked list.

But Merge Sort primarily requires:

  • Splitting the list
  • Traversing nodes
  • Merging sorted lists

These operations work naturally with linked lists.

Therefore, Merge Sort is a common choice for sorting linked lists.


5. External Sorting

What if the dataset is too large to fit into memory?

Suppose you have:

500 GB of data
Enter fullscreen mode Exit fullscreen mode

but only:

16 GB RAM
Enter fullscreen mode Exit fullscreen mode

You cannot load everything into memory at once.

External Merge Sort can:

  1. Read manageable chunks.
  2. Sort each chunk.
  3. Store sorted chunks.
  4. Merge those sorted chunks.

This makes Merge Sort useful for large-scale data processing and external storage.


The Bigger Picture

Merge Sort connects several concepts we've already learned.

Big-O

We just learned that:

O(n log n)
Enter fullscreen mode Exit fullscreen mode

is generally much more scalable than:

O(n²)
Enter fullscreen mode Exit fullscreen mode

Merge Sort is one of the classic algorithms that achieves O(n log n) sorting.

Binary Search

Binary Search taught us the power of:

Divide the search space.
Enter fullscreen mode Exit fullscreen mode

Merge Sort applies a related idea:

Divide the problem.
Solve smaller problems.
Combine the results.
Enter fullscreen mode Exit fullscreen mode

Recursion

Merge Sort is a classic example of recursion.

A large problem becomes smaller versions of the same problem:

sort(large array)
       ↓
sort(left half)
sort(right half)
       ↓
merge
Enter fullscreen mode Exit fullscreen mode

Divide and Conquer

This is the deeper lesson.

The pattern is:

                 Problem
                    ↓
              Divide it
              /       \
         Subproblem  Subproblem
              \       /
               Solve
                 ↓
                Merge
Enter fullscreen mode Exit fullscreen mode

This strategy appears in many algorithms beyond sorting.


The Most Important Mental Model

Don't memorize Merge Sort as a collection of recursive function calls.

Remember:

Split until the pieces are easy, then merge them back in sorted order.

The entire algorithm can be summarized as:

        [8 3 5 4 7 6 1 2]
                 ↓
          Split repeatedly
                 ↓
       [8] [3] [5] [4] [7] [6] [1] [2]
                 ↓
          Merge sorted pairs
                 ↓
       [3 8] [4 5] [6 7] [1 2]
                 ↓
          Merge larger groups
                 ↓
       [3 4 5 8] [1 2 6 7]
                 ↓
             Final merge
                 ↓
       [1 2 3 4 5 6 7 8]
Enter fullscreen mode Exit fullscreen mode

The algorithm doesn't magically know where every element belongs.

It makes the problem manageable by ensuring that every merge combines two already-sorted sequences.


Summary

Merge Sort is a comparison-based sorting algorithm based on Divide and Conquer.

The process is:

Divide
  ↓
Sort smaller pieces
  ↓
Merge
Enter fullscreen mode Exit fullscreen mode

Important points:

  • It repeatedly divides the array into halves.
  • Single-element arrays are considered sorted.
  • Sorted portions are merged together.
  • Time complexity is O(n log n) in best, average, and worst cases.
  • Standard implementations require O(n) auxiliary space.
  • Merge Sort is stable.
  • It works particularly well with linked lists.
  • It can be adapted for external sorting.
  • It teaches the important Divide and Conquer technique.

The progression is:

Binary Search
     ↓
Divide the search space
     ↓
Merge Sort
     ↓
Divide the problem
     ↓
Solve smaller problems
     ↓
Combine the results
Enter fullscreen mode Exit fullscreen mode

Top comments (0)