Merge Sort and Quicksort in Python, Timed Against Each Other

Merge sort splits the input in half, sorts each half recursively, and merges the two sorted halves back together. Quicksort picks a pivot, partitions the input around it, and recurses on the two partitions. This post runs both on a small list to check correctness, then times both on the same 5000 random integers.

The code

import random
import time


def merge_sort(arr):
    # Split in half, sort each half recursively, then merge two sorted
    # halves into one sorted list.
    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


def quicksort(arr):
    # Pick a pivot, partition into less-than/equal/greater-than, recurse
    # on the two unequal partitions.
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]
    less = [x for x in arr if x < pivot]
    equal = [x for x in arr if x == pivot]
    greater = [x for x in arr if x > pivot]
    return quicksort(less) + equal + quicksort(greater)


small = [38, 27, 43, 3, 9, 82, 10]
print("input:", small)
print("merge_sort:", merge_sort(small))
print("quicksort: ", quicksort(small))

random.seed(42)
big = [random.randint(0, 1_000_000) for _ in range(5000)]

start = time.perf_counter()
merge_result = merge_sort(big)
merge_time = time.perf_counter() - start

start = time.perf_counter()
quick_result = quicksort(big)
quick_time = time.perf_counter() - start

print(f"\ntiming on {len(big)} random ints (seed=42):")
print(f"merge_sort: {merge_time:.6f}s")
print(f"quicksort:  {quick_time:.6f}s")
print("both produced the same sorted result:", merge_result == quick_result)
print("matches Python's sorted():", merge_result == sorted(big))

Both functions run against the same 7-element list first, then against the same 5000-element list of random integers (random.seed(42) fixes the sequence so the input is reproducible). The script also checks merge_result == quick_result and compares both against Python’s built-in sorted().

Running it

Real output:

input: [38, 27, 43, 3, 9, 82, 10]
merge_sort: [3, 9, 10, 27, 38, 43, 82]
quicksort:  [3, 9, 10, 27, 38, 43, 82]

timing on 5000 random ints (seed=42):
merge_sort: 0.005712s
quicksort:  0.004581s
both produced the same sorted result: True
matches Python's sorted(): True

On the small list, both functions produced the identical sorted result [3, 9, 10, 27, 38, 43, 82]. On 5000 random integers, merge_sort took 0.005712s and quicksort took 0.004581s in this run, and both checks passed: merge_result == quick_result and merge_result == sorted(big) both printed True.

Takeaway

Both implementations agreed with each other and with Python’s own sorted() on 5000 random integers, and in this run quicksort measured faster than merge sort on that input — 0.004581s versus 0.005712s.