Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 28

Implementing Merge Sort

Recommended resources

In our last lesson, we implemented insertion sort, an intuitive algorithm that works well for small or nearly-sorted datasets but has an average-case time complexity. While effective, this quadratic scaling becomes a bottleneck for larger inputs. Today, we'll explore a significantly more efficient algorithm: merge sort.

This lesson will connect back to the concept of recursion you learned earlier. Merge sort is a prime example of the "divide and conquer" strategy, a powerful algorithmic paradigm you've likely encountered in your CSE studies. By recursively breaking the sorting problem into smaller, more manageable pieces, merge sort achieves a consistent and impressive time complexity. Our goal is to understand this strategy, implement it in Java, and analyze its performance trade-offs.

1. The "Divide and Conquer" Strategy

The core idea of merge sort is elegant and powerful. Instead of iterating through the list and inserting elements one by one, it systematically deconstructs and reconstructs the array.

  1. Divide: The algorithm recursively divides the input array into two halves until it can no longer be divided. This happens when the sub-arrays contain only one element. An array with a single element is, by definition, already sorted.
  2. Conquer (and Combine): After the division phase is complete, the algorithm starts merging the tiny, sorted sub-arrays back together. It takes two adjacent sorted sub-arrays and combines them into a single, larger sorted sub-array. This merging process continues up the chain of recursion until we are left with a single, fully sorted array.

The following diagram beautifully illustrates this entire process. The red arrows show the "divide" phase, where the array is recursively split. The green arrows show the "conquer" phase, where sorted sub-arrays are merged back together.

This diagram shows an array being recursively split into single-element arrays (the "divide" step) and then merged back into progressively larger sorted arrays (the "conquer" step) until the final sorted array is produced.

For a more detailed explanation of this two-step process, Baeldung's article provides a concise overview.

Merge Sort in Java

This article introduces merge sort as a classic "divide and conquer" algorithm.

Read the section titled The Algorithm. Focus on the definitions of the Divide and Conquer steps, as they form the conceptual backbone of this method.


2. The Heart of the Algorithm: The merge Operation

The "divide" step is straightforward—it's just about finding the midpoint. The real magic happens during the "conquer" phase, within a helper function typically called merge.

The merge function's job is to take two already sorted sub-arrays and combine them into a single, larger sorted array. Here's how it works:

  1. Create a temporary array to hold the merged result.
  2. Use two "pointers" (which are just index variables), i and j, starting at the beginning of the left and right sub-arrays, respectively.
  3. Compare the elements at arr1[i] and arr2[j].
  4. Copy the smaller of the two elements into the temporary array and advance the pointer of the array from which the element was taken.
  5. Repeat this process until one of the sub-arrays has been fully copied.
  6. Finally, copy any remaining elements from the other sub-array into the temporary array. This is simple because we know all remaining elements are already sorted and are larger than what has been merged so far.

The following video provides an excellent visualization of this merge process. Understanding this step is key to understanding the entire algorithm.

Merge Sort Algorithm in Java - Full Tutorial with Source

This "Coding with John" video gives a fantastic conceptual walkthrough of both the dividing and merging phases.

Watch the segment from the merging explanation. Pay close attention to how he compares the first elements of the two sorted sub-arrays and progressively builds the new, larger sorted array. This visual will make the code much more intuitive.


3. Java Implementation

Now, let's translate this logic into Java code. We'll need two methods:

  1. mergeSort(int[] array): The main recursive function that handles the "divide" step.
  2. merge(int[] array, int[] leftHalf, int[] rightHalf): The helper function that handles the "conquer" step.

The Recursive mergeSort Method

The mergeSort function implements the recursive division:

  • Base Case: If the input array has fewer than two elements, it is already sorted, so we simply return. This is the crucial condition that stops the recursion.
  • Recursive Step:
    1. Find the middle index of the array.
    2. Create two new arrays, leftHalf and rightHalf.
    3. Copy the corresponding elements from the input array into these new halves.
    4. Recursively call mergeSort on leftHalf.
    5. Recursively call mergeSort on rightHalf.
    6. Once both halves are sorted, call the merge method to combine them back into the original array.

The merge Helper Method

This method implements the logic we saw in the video: using three index variables (i, j, k) to iterate through the left half, the right half, and the main array, respectively, copying the smallest element at each step.

The "Coding with John" video provides a clear, step-by-step implementation of both methods.

Merge Sort Algorithm in Java - Full Tutorial with Source

The video now transitions from concept to a live coding session, building the entire algorithm from scratch.

First, watch how the recursive mergeSort method is constructed. Note the base case and how the array is split. Then, watch the implementation of the merge helper method, which combines the sorted halves. The code directly reflects the logic discussed earlier.

After watching the construction, you can review the final, clean code in the Baeldung article.

Merge Sort in Java

This section from Baeldung presents the complete, polished Java code for both the mergeSort and merge functions.

Review the code blocks for the mergeSort function and the merge function. This is the code that brings the entire divide-and-conquer strategy to life.


4. Performance Analysis

Merge sort's primary advantage is its performance. Unlike insertion sort, its efficiency doesn't depend on the initial order of the data.

Complexity Big O Notation
Worst case time
Best case time
Average case time
Space

Time Complexity:

This is a hallmark of efficient comparison-based sorting algorithms. The reason for this complexity comes from the two-step process:

  • Divisions: The array is repeatedly halved. The number of times you can halve a list of n items before you get down to single-element lists is . This represents the depth of the recursion.
  • Work per Level: At each level of recursion, every element of the original array is processed once during the merge step. For example, at the top level, we merge two arrays of size into one of size , which takes operations. At the level below, we perform two merges on arrays of size , for a total of operations. The total work at every level is .

Since we have levels, and we do work at each level, the total time complexity is .

Space Complexity:

This is the main trade-off of merge sort. The implementation we've used creates temporary arrays (leftHalf and rightHalf) at each step of the recursion. While an in-place merge is possible, it's significantly more complex. In this standard implementation, the space required for these temporary arrays is proportional to the size of the input array, leading to space complexity. This contrasts with insertion sort's space complexity.

Sorting (Bubble, Selection, Insertion, Merge, Quick, ...

VisuAlgo provides an excellent conceptual breakdown of why Merge Sort's complexity is what it is.

Read the section that analyzes the work done at each level of recursion. This is the key to understanding the n log n runtime. Then, read about the pros and cons, which highlight the guaranteed performance versus the extra memory requirement.


Conclusion

You have now implemented merge sort, a powerful and efficient sorting algorithm that serves as a cornerstone of computer science. It's a fantastic demonstration of how a recursive, divide-and-conquer approach can solve complex problems far more effectively than a simple iterative one, especially as data scales.

Key takeaways from this lesson:

  • Paradigm: Merge sort uses a "divide and conquer" strategy: recursively splitting the array and then merging the sorted halves.
  • Core Logic: The merge helper function, which efficiently combines two sorted arrays, is the most critical part of the algorithm.
  • Performance: It has a consistent time complexity of for all cases (best, average, and worst), making it highly reliable for large datasets.
  • Trade-off: Its primary drawback is the space complexity required for the temporary arrays used during the merge process.

In the next lesson, we'll shift from implementing sorting algorithms from scratch to using Java's built-in, highly optimized sorting utilities. You'll learn how to sort collections of objects by defining their natural order using the Comparable interface and how to provide custom sorting logic using the Comparator interface. This will bridge the gap between theoretical algorithm knowledge and practical, everyday Java programming.