Merge sort splits the array in half, sorts each half and merges them: always O(n log n), stable, but it needs O(n) extra memory. Quick sort picks a pivot and partitions the array around it in place: O(n log n) on average with little extra memory, not stable, and O(n²) in the worst case unless the pivot is chosen well. In our benchmarks a good in-place quicksort was 1.5–2.5× faster than merge sort on random numbers.
Tested with: Node.js 20.20 and Python 3.14, October 2026. Both implementations passed 300 random fuzz tests against the built-in sort, and every timing below was measured (best of 3 runs).
Merge Sort vs Quick Sort at a Glance
| Merge sort | Quick sort | |
|---|---|---|
| Average time | O(n log n) | O(n log n) |
| Worst time | O(n log n) | O(n²) (bad pivots); rare with a random pivot |
| Extra memory | O(n) | O(log n) stack (in-place version) |
| Stable (keeps equal items in order) | Yes, if ties take from the left | No |
| In place | No | Yes |
| Linked lists | Great (no random access needed) | Poor |
| Data bigger than memory (external sort) | Yes, merging sorted chunks from disk | No |
| Typical use | Stable sorting, linked lists, files, parallel sorting | Fast in-memory sorting of numbers |
n is the number of items. If complexity notation is new to you, see our Big-O guide.
Merge Sort in JavaScript
const ascending = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
// Stable merge sort: returns a new sorted array
export function mergeSort(arr, compare = ascending) {
if (arr.length <= 1) return arr.slice();
const mid = arr.length >> 1;
const left = mergeSort(arr.slice(0, mid), compare);
const right = mergeSort(arr.slice(mid), compare);
const result = new Array(arr.length);
let i = 0, j = 0, k = 0;
while (i < left.length && j < right.length) {
// "<= 0" takes from the left on ties, which keeps equal items in order
result[k++] = compare(left[i], right[j]) <= 0 ? left[i++] : right[j++];
}
while (i < left.length) result[k++] = left[i++];
while (j < right.length) result[k++] = right[j++];
return result;
}- Divide: split until each piece has one item (already sorted).
- Conquer: merge two sorted halves by repeatedly taking the smaller front item.
- The "<= 0" is what makes it stable. On a tie it takes from the left half, so equal items keep their original order.
// The common version: "<" sends ties to the right side first
if (left[i] < right[j]) result.push(left[i++]);
else result.push(right[j++]);
// Sorting the people above by age gave: Dee Ben Eli Cy Ana (not stable)We ran that common version: same-age people came out in a different order, so it isn't stable even though many tutorials (including an older version of this one) say it is.
Quick Sort in JavaScript (In Place)
const ascending = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
function swap(arr, i, j) {
const tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// In-place quicksort: random pivot + three-way partition, so sorted input
// and many duplicates don't cause O(n²) time or deep recursion
export function quickSort(arr, compare = ascending, lo = 0, hi = arr.length - 1) {
while (lo < hi) {
const pivot = arr[lo + Math.floor(Math.random() * (hi - lo + 1))];
// Partition into < pivot | == pivot | > pivot
let lt = lo;
let i = lo;
let gt = hi;
while (i <= gt) {
const c = compare(arr[i], pivot);
if (c < 0) swap(arr, lt++, i++);
else if (c > 0) swap(arr, i, gt--);
else i++;
}
// Recurse into the smaller side, loop on the larger: stack depth O(log n)
if (lt - lo < hi - gt) {
quickSort(arr, compare, lo, lt - 1);
lo = gt + 1;
} else {
quickSort(arr, compare, gt + 1, hi);
hi = lt - 1;
}
}
return arr;
}import { mergeSort } from "./merge-sort.js";
import { quickSort } from "./quick-sort.js";
mergeSort([38, 27, 43, 3, 9, 82, 10]); // [3, 9, 10, 27, 38, 43, 82] (new array)
quickSort([38, 27, 43, 3, 9, 82, 10]); // [3, 9, 10, 27, 38, 43, 82] (same array, sorted in place)
// Both accept a compare function, like Array.prototype.sort
mergeSort(users, (a, b) => a.age - b.age);- Random pivot: the classic "last element" pivot is the worst possible choice for already-sorted input.
- Three-way partition: items equal to the pivot are grouped in the middle and never touched again, so arrays with many duplicates stay fast (1 million numbers with only 10 distinct values: 40 ms, versus 660 ms for merge sort).
- Recurse on the smaller side: the larger side is handled by the loop, which keeps the recursion depth around log n even in bad cases.
Why the "simple" quicksort fails
// Popular "simple" quicksort: not in place, last element as pivot
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[arr.length - 1];
const left = [], right = [];
for (let i = 0; i < arr.length - 1; i++) {
(arr[i] < pivot ? left : right).push(arr[i]);
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
quickSort(Array.from({ length: 10000 }, (_, i) => i));
// RangeError: Maximum call stack size exceededOn already-sorted input every pivot is the largest value, so each call peels off just one item: O(n²) time and n levels of recursion. In our test it took 197 ms for 5,000 sorted numbers and crashed at 10,000. The Python version of the same code raised RecursionError at 2,000. It's also not in place: it allocates new arrays at every level.
Benchmarks: Which Is Faster?
Node.js 20.20, numbers, best of 3 runs, times in milliseconds:
| Input | quickSort (above) | mergeSort (above) | Array.prototype.sort |
|---|---|---|---|
| 10,000 random | 2 | 5 | 2 |
| 100,000 random | 33 | 77 | 47 |
| 1,000,000 random | 590 | 901 | 589 |
| 1,000,000 already sorted | 263 | 502 | 16 |
| 1,000,000, only 10 distinct values | 40 | 660 | 162 |
- On random data quicksort beat merge sort by 1.5–2.5×: it works in place, so it touches less memory and allocates nothing.
- The built-in sort was as fast as our quicksort on random data and far faster on sorted data: V8 uses TimSort, a merge-sort hybrid that detects already-sorted runs (V8 blog: Getting things sorted).
- Merge sort's strength isn't raw speed on arrays; it's the guarantees: stable, predictable O(n log n), and works on linked lists and data streamed from disk.
In Python 3.14 (100,000 random floats) our quick_sort took 256 ms, merge_sort 281 ms, and the built-in sorted() 14 ms, because it's TimSort written in C. In real Python code, always use sorted() or list.sort().
Merge Sort and Quick Sort in Python
import random
def merge_sort(arr):
if len(arr) <= 1:
return arr[:]
mid = len(arr) // 2
left, right = merge_sort(arr[:mid]), merge_sort(arr[mid:])
result, i, j = [], 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]: # <= keeps equal items in order (stable)
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
def quick_sort(arr, lo=0, hi=None):
"""In-place, random pivot, three-way partition."""
if hi is None:
hi = len(arr) - 1
while lo < hi:
pivot = arr[random.randint(lo, hi)]
lt, i, gt = lo, lo, hi
while i <= gt:
if arr[i] < pivot:
arr[lt], arr[i] = arr[i], arr[lt]
lt += 1
i += 1
elif arr[i] > pivot:
arr[i], arr[gt] = arr[gt], arr[i]
gt -= 1
else:
i += 1
if lt - lo < hi - gt:
quick_sort(arr, lo, lt - 1)
lo = gt + 1
else:
quick_sort(arr, gt + 1, hi)
hi = lt - 1
return arr
nums = [38, 27, 43, 3, 9, 82, 10]
print(merge_sort(nums)) # [3, 9, 10, 27, 38, 43, 82]
print(quick_sort(nums[:])) # [3, 9, 10, 27, 38, 43, 82]Both passed the same 300 random tests against sorted(). As in JavaScript, <= in the merge keeps it stable.
Stability: Why It Matters
const people = [
{ name: "Ana", age: 30 }, { name: "Ben", age: 25 }, { name: "Cy", age: 30 },
{ name: "Dee", age: 25 }, { name: "Eli", age: 30 },
];
const byAge = (a, b) => a.age - b.age;
mergeSort(people, byAge); // Ben Dee Ana Cy Eli ← same-age people keep their order
[...people].sort(byAge); // Ben Dee Ana Cy Eli ← built-in sort is stable too
quickSort([...people], byAge); // e.g. Dee Ben Cy Eli Ana (order of equals varies)Stability matters when you sort by one field after another: sort a table by name, then by department, and a stable sort keeps names in order inside each department. JavaScript's built-in sort() has been required to be stable since ES2019; our test confirmed it. Sorting by dates? See sorting an object array by date.
What Do Real Languages Use?
| Language / engine | Default sort |
|---|---|
| JavaScript (V8: Chrome, Node.js) | TimSort (stable), since 2018 |
| Python (sorted, list.sort) | TimSort (stable); Python 3.11+ uses Powersort merge rules |
| Java | Dual-pivot quicksort for primitives; TimSort for objects (stable) |
| C++ std::sort | Introsort (quicksort that switches to heap sort if recursion gets too deep); std::stable_sort is merge-based |
Notice the pattern: libraries use quicksort variants where stability doesn't matter (numbers) and merge-based sorts where it does (objects). Introsort's fallback is heap sort, which guarantees O(n log n) in place but isn't stable.
When to Use Merge Sort vs Quick Sort
| Situation | Choose |
|---|---|
| You need equal items to keep their order | Merge sort (or the built-in sort) |
| Sorting a linked list | Merge sort |
| Data doesn't fit in memory | Merge sort (external merge sort) |
| Guaranteed O(n log n) for untrusted input | Merge sort, or heap sort/introsort |
| Fastest in-memory sort of numbers, little extra memory | Quick sort with random pivot and 3-way partition |
| Interview: "sort in place with O(1) extra space" | Quick sort (or heap sort) |
| Integers in a small range | Neither: counting or radix sort |
| Real application code | The built-in sort |
For integer keys, non-comparison sorts can beat both: see radix sort vs quicksort. For a step-by-step walkthrough of partitioning, see Quicksort explained, and for every common algorithm side by side, our sorting algorithms in JavaScript guide.
FAQs
Which is faster, merge sort or quick sort?
On arrays in memory, a well-implemented quicksort is usually faster: 1.5–2.5× in our Node.js benchmarks. Merge sort is more predictable and wins when you need stability, linked lists or external sorting.
Why is quick sort faster if both are O(n log n)?
Big-O hides constant factors. Quicksort works in place with sequential memory access and no allocations; merge sort copies data into new arrays at every level.
Is merge sort stable? Is quick sort stable?
Merge sort is stable when the merge takes from the left half on ties (<=). Quicksort is not stable.
What is the worst case of quick sort and how do you avoid it?
O(n²), when pivots repeatedly split the array very unevenly, for example the last element of an already-sorted array. A random pivot (or median-of-three), three-way partitioning for duplicates, and recursing into the smaller side avoid it in practice.
Is quick sort in place?
The proper implementation is (only O(log n) stack). Versions that build left and right arrays, common in tutorials, are not.
What sorting algorithm does JavaScript's sort() use?
In V8 (Chrome, Node.js, Edge) it's TimSort, a stable hybrid of merge sort and insertion sort. Other engines also implement a stable sort, as the spec requires.
Next: BFS vs DFS for the graph side of algorithm interviews.
Related Articles
View allAbout Muhaymin Bin Mehmood
Front-end Developer skilled in the MERN stack, experienced in web and mobile development. Proficient in React.js, Node.js, and Express.js, with a focus on client interactions, sales support, and high-performance applications.




