Skip to content
intermediatePhase 3 · Sorting & Searching

Merge Sort

Master divide-and-conquer sorting with guaranteed O(n log n) performance.

1h 15m
5 problems
Topic Progress0%

Merge Sort Fundamentals

Merge sort is a divide-and-conquer algorithm that divides the array into halves, recursively sorts them, then merges the sorted halves.

Algorithm Steps

  1. Divide: Split array into two halves
  2. Conquer: Recursively sort each half
  3. Combine: Merge the two sorted halves

Visual Example

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

Divide Phase:
[38, 27, 43, 3] → [38, 27] [43, 3] → [38] [27] [43] [3]
[9, 82, 10] → [9, 82] [10] → [9] [82] [10]

Merge Phase:
[27, 38] [3, 43] → [3, 27, 38, 43]
[9, 82] [10] → [9, 10, 82]

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

Java Implementation

public void mergeSort(int[] arr, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;
        
        // Sort first and second halves
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        
        // Merge sorted halves
        merge(arr, left, mid, right);
    }
}

private void merge(int[] arr, int left, int mid, int right) {
    // Create temporary arrays
    int[] leftArr = Arrays.copyOfRange(arr, left, mid + 1);
    int[] rightArr = Arrays.copyOfRange(arr, mid + 1, right + 1);
    
    int i = 0, j = 0, k = left;
    
    // Merge elements in sorted order
    while (i < leftArr.length && j < rightArr.length) {
        if (leftArr[i] <= rightArr[j]) {
            arr[k++] = leftArr[i++];
        } else {
            arr[k++] = rightArr[j++];
        }
    }
    
    // Copy remaining elements
    while (i < leftArr.length) {
        arr[k++] = leftArr[i++];
    }
    while (j < rightArr.length) {
        arr[k++] = rightArr[j++];
    }
}

In-Place Merge Sort (Optimized)

public void mergeSortInPlace(int[] arr, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;
        mergeSortInPlace(arr, left, mid);
        mergeSortInPlace(arr, mid + 1, right);
        mergeInPlace(arr, left, mid, right);
    }
}

private void mergeInPlace(int[] arr, int left, int mid, int right) {
    int start2 = mid + 1;
    
    // If already sorted, no need to merge
    if (arr[mid] <= arr[start2]) return;
    
    while (left <= mid && start2 <= right) {
        if (arr[left] <= arr[start2]) {
            left++;
        } else {
            int value = arr[start2];
            int index = start2;
            
            // Shift elements right
            while (index != left) {
                arr[index] = arr[index - 1];
                index--;
            }
            arr[left] = value;
            
            left++;
            mid++;
            start2++;
        }
    }
}

Complexity Analysis

Aspect Complexity
Time (all cases) O(n log n)
Space O(n) - temporary arrays
Stable Yes - preserves order of equal elements

Merge Sort Applications

Merge Sort for Linked Lists

Merge sort is ideal for linked lists due to:

  1. No random access needed (unlike quicksort)
  2. O(1) merge (just relink pointers)
  3. O(log n) space for recursion stack only
public ListNode mergeSortList(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    
    // Find middle using slow/fast pointers
    ListNode slow = head, fast = head.next;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    
    ListNode mid = slow.next;
    slow.next = null;  // Split list
    
    // Recursively sort halves
    ListNode left = mergeSortList(head);
    ListNode right = mergeSortList(mid);
    
    // Merge sorted lists
    return mergeLists(left, right);
}

private ListNode mergeLists(ListNode l1, ListNode l2) {
    ListNode dummy = new ListNode(0);
    ListNode current = dummy;
    
    while (l1 != null && l2 != null) {
        if (l1.val <= l2.val) {
            current.next = l1;
            l1 = l1.next;
        } else {
            current.next = l2;
            l2 = l2.next;
        }
        current = current.next;
    }
    
    current.next = (l1 != null) ? l1 : l2;
    return dummy.next;
}

External Merge Sort

Used for sorting files larger than memory:

  1. Divide file into chunks that fit in memory
  2. Sort each chunk in memory
  3. Merge sorted chunks using k-way merge

When to Use Merge Sort

Use when:

  • Guaranteed O(n log n) needed
  • Stability is required
  • Sorting linked lists
  • External sorting (large files)

Avoid when:

  • Memory is limited (use quicksort in-place)
  • Array is nearly sorted (use insertion sort)
  • Stability not needed (quicksort may be faster in practice)

Comparison with Other Sorts

Algorithm Time (avg) Time (worst) Space Stable
Merge Sort O(n log n) O(n log n) O(n) Yes
Quick Sort O(n log n) O(n²) O(log n) No
Heap Sort O(n log n) O(n log n) O(1) No

Interactive Visualization

Merge Sort Split & Merge

Press Play or Step to begin
CurrentFound / DoneEliminatedUnvisited

Practice Problems

0/3solved
Merge k Sorted Lists
Divide and Conquer

Given an array of k linked-lists sorted in ascending order, merge all into a single sorted list.

Example:

Input: lists = [[1,4,5],[1,3,4],[2,6]]

Output: [1,1,2,3,4,4,5,6]

Merge three sorted lists into one sorted list.

Optimal Solution — O(N log k) time, O(log k) space

Divide and conquer - pairwise merge lists

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) return null;
        return mergeLists(lists, 0, lists.length - 1);
    }
    private ListNode mergeLists(ListNode[] lists, int lo, int hi) {
        if (lo == hi) return lists[lo];
        int mid = lo + (hi - lo) / 2;
        ListNode left = mergeLists(lists, lo, mid);
        ListNode right = mergeLists(lists, mid + 1, hi);
        return mergeTwo(left, right);
    }
    private ListNode mergeTwo(ListNode l1, ListNode l2) {
        if (l1 == null) return l2;
        if (l2 == null) return l1;
        if (l1.val <= l2.val) { l1.next = mergeTwo(l1.next, l2); return l1; }
        else { l2.next = mergeTwo(l1, l2.next); return l2; }
    }
}

Edge Cases:

  • Empty list
  • Single list
  • All same values
Sort List
Merge Sort on Linked List

Sort a linked list in O(n log n) time using merge sort.

Example:

Input: head = [4,2,1,3]

Output: [1,2,3,4]

Merge sort on linked list.

Optimal Solution — O(n log n) time, O(log n) space

Merge sort: find middle, recursively sort halves, merge

class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode slow = head, fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode mid = slow.next;
        slow.next = null;
        ListNode left = sortList(head);
        ListNode right = sortList(mid);
        return merge(left, right);
    }
    private ListNode merge(ListNode l1, ListNode l2) {
        if (l1 == null) return l2;
        if (l2 == null) return l1;
        if (l1.val <= l2.val) { l1.next = merge(l1.next, l2); return l1; }
        else { l2.next = merge(l1, l2.next); return l2; }
    }
}

Edge Cases:

  • Empty list
  • Single node
  • Already sorted
  • Reverse sorted
Count of Smaller Numbers After Self
Merge Sort with Counting

Given an integer array nums, return an integer array counts where counts[i] is the number of smaller elements to the right of nums[i].

Example:

Input: nums = [5,2,6,1]

Output: [2,1,1,0]

To the right of 5: 2,1 (2 smaller). To the right of 2: 1 (1 smaller).

Optimal Solution — O(n log n) time, O(n) space

Merge sort while counting inversions

class Solution {
    public List<Integer> countSmaller(int[] nums) {
        int n = nums.length;
        Integer[] result = new Integer[n];
        int[][] pairs = new int[n][2];
        for (int i = 0; i < n; i++) { pairs[i][0] = nums[i]; pairs[i][1] = i; }
        mergeSort(pairs, 0, n - 1, result);
        return Arrays.asList(result);
    }
    private void mergeSort(int[][] pairs, int lo, int hi, Integer[] result) {
        if (lo >= hi) return;
        int mid = lo + (hi - lo) / 2;
        mergeSort(pairs, lo, mid, result);
        mergeSort(pairs, mid + 1, hi, result);
        merge(pairs, lo, mid, hi, result);
    }
    private void merge(int[][] pairs, int lo, int mid, int hi, Integer[] result) {
        int[][] temp = new int[hi - lo + 1][];
        int i = lo, j = mid + 1, k = 0, count = 0;
        while (i <= mid && j <= hi) {
            if (pairs[i][0] <= pairs[j][0]) { result[pairs[i][1]] += count; temp[k++] = pairs[i++]; }
            else { count++; temp[k++] = pairs[j++]; }
        }
        while (i <= mid) { result[pairs[i][1]] += count; temp[k++] = pairs[i++]; }
        while (j <= hi) temp[k++] = pairs[j++];
        System.arraycopy(temp, 0, pairs, lo, temp.length);
    }
}

Edge Cases:

  • All same elements
  • Strictly increasing
  • Strictly decreasing

Quiz

1. What is the time complexity of merge sort?

Question 1 options

2. Why is merge sort preferred for linked lists over quicksort?

Question 2 options

3. What is the primary purpose of Merge Sort?

Question 3 options

4. What is a common mistake when implementing Merge Sort?

Question 4 options

Flashcards

Question

What are the three phases of merge sort?

Answer

1. Divide: Split array into halves. 2. Conquer: Recursively sort halves. 3. Combine: Merge sorted halves.

Question

Why is merge sort stable?

Answer

During merge step, when elements are equal, we take from left half first, preserving original order.

Question

What is Merge Sort?

Answer

Merge Sort is a key concept in software engineering.

Question

When to use Merge Sort?

Answer

Use Merge Sort when building production systems that require reliability, scalability, and maintainability.

Question

Merge Sort best practices

Answer

Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.

Revision Notes

Key Takeaways

  • 1.Merge sort provides guaranteed O(n log n) performance
  • 2.It is stable, preserving order of equal elements
  • 3.Space complexity is O(n) due to temporary arrays
  • 4.Ideal for linked lists where merge is O(1)

Interview Tips

  • Discuss time/space tradeoffs compared to quicksort
  • Explain why merge sort is preferred for linked lists
  • Mention stability requirement as a reason to choose merge sort
  • Be prepared to implement both array and linked list versions

Cheat Sheet

Merge Sort Cheat Sheet

Algorithm:

  1. Divide array into two halves
  2. Recursively sort each half
  3. Merge sorted halves

Complexity:

  • Time: O(n log n) all cases
  • Space: O(n) for temporary arrays
  • Stable: Yes

Implementation:

void mergeSort(int[] arr, int l, int r) {
    if (l < r) {
        int m = l + (r - l) / 2;
        mergeSort(arr, l, m);
        mergeSort(arr, m + 1, r);
        merge(arr, l, m, r);
    }
}

When to Use:

  • Guaranteed O(n log n)
  • Stability required
  • Linked list sorting
  • External sorting