Timsort

Timsort is a hybrid stable sort derived from mergesort and insertion sort. It is the algorithm behind Python's built-in sorted() and list.sort(), and Java's Arrays.sort on object arrays.

The sections below trace run detection on a concrete array, explain the run stack and galloping merges, and give a simplified implementation that shows the core ideas.

1. Complexity

Best Average Worst Space (Worst)
Ω(n) Θ(n log n) O(n log n) O(n)

2. How it works

Walk the input identifying naturally-ordered subsequences ("runs"). Short runs are extended to a minimum length using insertion sort. Runs are pushed onto a stack and merged according to invariants that balance run lengths, keeping the merge tree shallow. The result is O(n) on already-sorted or reverse-sorted input and O(n log n) otherwise — and stable.

3. Python implementation

# In production: just call sorted() — it IS Timsort.
data = [3, 6, 1, 8, 2, 9, 4]
print(sorted(data))           # [1, 2, 3, 4, 6, 8, 9]
data.sort()                   # in-place
print(data)


# Sketch of the core idea: detect runs, extend short ones with
# insertion sort, then merge runs pairwise.
def insertion_sort(a, lo, hi):
    for i in range(lo + 1, hi):
        x = a[i]; j = i - 1
        while j >= lo and a[j] > x:
            a[j + 1] = a[j]; j -= 1
        a[j + 1] = x


def timsort_sketch(a, min_run=32):
    n = len(a)
    for start in range(0, n, min_run):
        insertion_sort(a, start, min(start + min_run, n))
    size = min_run
    while size < n:
        for left in range(0, n, 2 * size):
            mid = min(left + size, n)
            right = min(left + 2 * size, n)
            a[left:right] = sorted(a[left:right])  # placeholder merge
        size *= 2
    return a

4. Trade-offs

5. Step-by-step trace: finding runs

Run Detection on [1, 4, 7, 9, 8, 5, 2, 3, 6] 1 4 7 9 8 5 2 3 6 run A: ascending 1 4 7 9 run B: descending 8 5 2 run C: 3 6 A kept: 1 4 7 9 B reversed: 2 5 8 C: 3 6 merge: 1 2 3 4 5 6 7 8 9

Timsort begins by scanning for runs: maximal stretches that are already non-decreasing, or strictly decreasing. In the example, 1 4 7 9 is an ascending run. The next stretch 8 5 2 is strictly descending, so Timsort reverses it in place to 2 5 8; the requirement of strict descent is what keeps the reversal stable, because no equal elements get swapped. The tail 3 6 is a short ascending run. Real implementations require each run to be at least minrun long (a value between 32 and 64 chosen from n), and extend shorter runs with binary insertion sort. With an input this small, the entire array would simply be insertion-sorted, but the trace shows the logic that applies at scale.

6. Run stack and merge policy

Run Stack and Merge Policy Scan next run natural or extended Extend to minrun binary insertion sort Push on stack Check invariants Merge adjacent runs (galloping) until invariants hold more input Invariants keep each run larger than the sum of the next two, so the stack stays O(log n) deep.

Each run is pushed onto a stack. After every push, the algorithm checks invariants on the top few run lengths and merges adjacent runs until they hold. The classic rules are that, for the top three run lengths A, B and C, A > B + C and B > C. These keep merges roughly balanced, like a mergesort tree, while letting long natural runs stay intact. Merges use a temporary buffer the size of the smaller run, and a galloping mode: when one run wins many comparisons in a row, Timsort switches to exponential search to find how many elements to copy in one block. On data with large ordered regions this skips most comparisons. Recent CPython versions replaced the original stack rules with the “powersort” merge policy, which computes merge order from run positions; the run detection, minrun and galloping ideas are unchanged.

7. Worked example: a simplified Timsort

from bisect import insort

MINRUN = 4

def find_run(a, lo):
    hi = lo + 1
    if hi == len(a):
        return hi
    if a[hi] < a[lo]:                      # strictly descending
        while hi < len(a) and a[hi] < a[hi - 1]:
            hi += 1
        a[lo:hi] = reversed(a[lo:hi])
    else:                                  # non-decreasing
        while hi < len(a) and a[hi] >= a[hi - 1]:
            hi += 1
    return hi

def merge(x, y):
    out, i, j = [], 0, 0
    while i < len(x) and j < len(y):
        if y[j] < x[i]:
            out.append(y[j]); j += 1
        else:
            out.append(x[i]); i += 1
    return out + x[i:] + y[j:]

def simple_timsort(data):
    a, runs, lo = list(data), [], 0
    while lo < len(a):
        hi = find_run(a, lo)
        run = a[lo:hi]
        while len(run) < MINRUN and hi < len(a):   # extend short run
            insort(run, a[hi]); hi += 1
        runs.append(run)
        lo = hi
    while len(runs) > 1:                            # pairwise merging
        runs = [merge(runs[k], runs[k + 1]) if k + 1 < len(runs) else runs[k]
                for k in range(0, len(runs), 2)]
    return runs[0] if runs else []

print(simple_timsort([1, 4, 7, 9, 8, 5, 2, 3, 6]))  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

This sketch keeps the three core ideas — run detection with reversal, extension to a minimum run length with insertion, and stable merging — and replaces the stack invariants and galloping with plain pairwise merges. It is for understanding only; use sorted() or list.sort() in real code, which are implemented in C. Note that insort is stable for already-placed items but inserts after equal elements, which matches the stable behaviour needed here.

8. When it shines

Timsort is the default sort in Python and is used by Java for object arrays and by Android and V8 for similar reasons. It performs best on real-world data that is partially ordered: appending a few records to a sorted list, sorting log lines that are mostly chronological, or concatenating several sorted files. Such inputs approach O(n). On uniformly random data it behaves like a well-tuned mergesort with O(n log n) comparisons and O(n) auxiliary space in the worst case. If memory is tightly constrained or stability does not matter, an in-place introsort may be preferable.

9. Pitfalls

Mergesort explains the merge step Timsort builds on, and Insertion Sort the method used to build minimum-length runs. Compare with Quicksort for an unstable in-place alternative.

← Back to Algorithms