Merge sort is useful when predictable O(n log n) time and stable ordering matter more than sorting in place.
Choose it for linked lists, external sorting, and records that may be sorted by more than one key. Avoid it on tight memory budgets where the extra merge buffer is the real constraint.
The important checks are the split condition, the merge loop, and whether equal values keep their original order.
This divide-and-conquer algorithm splits input until each run has one element, then combines sorted runs with a linear merge.
Its predictable O(n log n) running time holds for best, average, and worst cases. Taking an equal value from the left run first also preserves stable ordering.
Merge Sort works in two major phases:
The key operation is the merge step, where two already sorted subarrays are combined efficiently.
The array is repeatedly divided in half, so the number of division levels is about log n. At each level, all elements are processed once during merging, which costs O(n).
So the total work is:
O(n) work per level x O(log n) levels = O(n log n)
This is why Merge Sort has the same asymptotic running time in all cases.
| Metric | Best | Average | Worst | Space | Stable? |
|---|---|---|---|---|---|
| Time Complexity | O(n log n) | O(n log n) | O(n log n) | O(n) | Yes |
| Recurrence | T(n) = 2T(n/2) + O(n) | - | - | ||
| Recursion depth | O(log n) | - | - |
Consider the array [38, 27, 43, 3, 9, 82, 10].
Final result: [3, 9, 10, 27, 38, 43, 82]
import java.util.Arrays;
public class MergeSort {
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);
}
static void merge(int[] arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int[] leftPart = new int[n1];
int[] rightPart = new int[n2];
for (int i = 0; i < n1; i++) leftPart[i] = arr[left + i];
for (int j = 0; j < n2; j++) rightPart[j] = arr[mid + 1 + j];
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (leftPart[i] <= rightPart[j]) {
arr[k++] = leftPart[i++];
} else {
arr[k++] = rightPart[j++];
}
}
while (i < n1) arr[k++] = leftPart[i++];
while (j < n2) arr[k++] = rightPart[j++];
}
public static void main(String[] args) {
int[] arr = {38, 27, 43, 3, 9, 82, 10};
System.out.println("Before: " + Arrays.toString(arr));
mergeSort(arr, 0, arr.length - 1);
System.out.println("After: " + Arrays.toString(arr));
}
}
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
arr = [38, 27, 43, 3, 9, 82, 10]
print("Before:", arr)
print("After:", merge_sort(arr))
#include <iostream>
#include <vector>
using namespace std;
void merge(vector<int>& arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
vector<int> leftPart(n1), rightPart(n2);
for (int i = 0; i < n1; i++) leftPart[i] = arr[left + i];
for (int j = 0; j < n2; j++) rightPart[j] = arr[mid + 1 + j];
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (leftPart[i] <= rightPart[j]) arr[k++] = leftPart[i++];
else arr[k++] = rightPart[j++];
}
while (i < n1) arr[k++] = leftPart[i++];
while (j < n2) arr[k++] = rightPart[j++];
}
void mergeSort(vector<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);
}
int main() {
vector<int> arr = {38, 27, 43, 3, 9, 82, 10};
mergeSort(arr, 0, arr.size() - 1);
for (int x : arr) cout << x << " ";
return 0;
}
function merge(left, right) {
const result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
result.push(left[i++]);
} else {
result.push(right[j++]);
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
function mergeSort(arr) {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
let arr = [38, 27, 43, 3, 9, 82, 10];
console.log("Before:", arr);
console.log("After:", mergeSort(arr));
Suppose we want to merge [27, 38] and [3, 43].
This merge step is linear in the total size of the two sorted subarrays.
| Step | Compare | Chosen Element | Result So Far |
|---|---|---|---|
| 1 | 27 vs 3 | 3 | [3] |
| 2 | 27 vs 43 | 27 | [3, 27] |
| 3 | 38 vs 43 | 38 | [3, 27, 38] |
| 4 | Left side empty | 43 | [3, 27, 38, 43] |
Stability comes from choosing the left-hand element when merge compares equal keys. Records with equal keys therefore retain their earlier relative order.
Stable ordering is useful when records are sorted in stages, such as sorting employees by department and then by joining date without losing the earlier grouping.
During merging, temporary arrays are usually needed to store the left and right halves. Because of this, standard Merge Sort on arrays requires O(n) extra space.
This is the main tradeoff compared with Quick Sort or Heap Sort.
Linked lists suit merge sort because splitting and relinking do not require random access or a full temporary array:
These properties make merge sort a natural stable choice for linked-list structures.
Recursion is optional. A bottom-up implementation merges runs of size 1, then 2, then 4, doubling the run width until the array is sorted while retaining O(n log n) time.
Choose merge sort when one or more of these constraints matter:
| Feature | Merge Sort | Quick Sort | Heap Sort |
|---|---|---|---|
| Best / Average / Worst | O(n log n) | O(n log n) / O(n log n) / O(n^2) | O(n log n) |
| Extra space | O(n) | O(log n) | O(1) |
| Stable | Yes | No | No |
| Practical strength | Predictable and stable | Usually fastest for arrays | In-place with guaranteed worst case |
Explore 500+ free tutorials across 20+ languages and frameworks.