Cloud Tech by Victor
AlgorithmsIntermediate

Sorting Algorithms & Stability

Why a "stable" sort guarantees equal-key elements keep their original relative order, and how Python's Timsort exploits already-sorted runs in real data to hit O(n) on the best case instead of always paying O(n log n).

Updated 2026-07-243 min read

Overview

A sort's stability guarantee, whether elements comparing as equal keep their original relative order, is what makes sorting by multiple keys in separate passes actually correct: sort by the secondary key, then stably sort by the primary key, and equal-primary-key elements retain their secondary-key order from the first pass automatically. Python's built-in sort is both guaranteed stable and implemented as Timsort, which specifically detects and merges already-ordered runs already present in the input rather than treating every input as uniformly random, giving it O(n) on the best case (already-sorted input) instead of the flat O(n log n) a textbook mergesort always pays. Real-world data is very often partially sorted already, which is exactly the case Timsort is built to exploit, and it still special-cases small subarrays with simple insertion sort internally, since a lower-constant-factor O(n^2) approach genuinely wins at small sizes despite losing asymptotically.

Quick Reference

PropertyWhat it means
Stable sortEqual-key elements keep their original relative order
Timsort best caseO(n) - exploits existing sorted runs
Timsort average/worst caseO(n log n)
Multi-key sort via stabilitySort by secondary key, then stably sort by primary key

Syntax

python
# Stability lets you sort by multiple keys in separate passes.
by_age = sorted(students, key=lambda s: s.age)
by_grade_then_age = sorted(by_age, key=lambda s: s.grade)

Examples

python
data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
sorted(data, key=lambda x: x[0])
# [('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]
# 'blue' entries keep their original relative order - stability

Sort by secondary key first, then primary key, when keys are separate passes

Because the sort is stable, sorting by the secondary key first and then stably sorting by the primary key produces a correct combined sort, equal-primary-key elements keep their secondary-key order automatically. This only works because of the stability guarantee.

Visual Diagram

Common Mistakes

  • Assuming a general "sort" function is unstable and writing a single combined comparator for multi-key sorts, when a stable sort's simpler sequential-pass approach would work and be easier to reason about.
  • Assuming every sort is O(n log n) uniformly, missing that real implementations like Timsort adapt to already-ordered input for a much better best case.
  • Choosing a textbook O(n log n) algorithm for consistently small inputs, when a simpler O(n^2) approach would actually run faster due to lower constant factors.
  • Forgetting that stability is a guarantee the algorithm has to provide explicitly; not every general sorting algorithm (heapsort, for instance) is stable by default.

Performance

  • Timsort's best case (already or nearly sorted input) is O(n), a genuine, exploitable advantage over algorithms with a flat O(n log n) regardless of input order.
  • Small-subarray special-casing (falling back to insertion sort) is a real, deliberate optimization in production sort implementations, not an oversight, because constant factors dominate at small n.

Best Practices

  • Rely on stability for multi-key sorts done as sequential single-key passes, rather than writing one more complex combined comparator.
  • Don't assume O(n log n) is the only relevant number, check whether the actual data is likely to already be partially ordered, which real sort implementations exploit.
  • Trust a language's built-in general-purpose sort for typical use, it's very likely already tuned (stability, run detection, small-input special-casing) beyond what a hand-rolled implementation would bother with.
  • Verify a specific sort's stability guarantee explicitly before relying on it, rather than assuming all sorting algorithms provide it.

Interview questions

What does it mean for a sorting algorithm to be "stable," and why does that matter for sorting by multiple keys in separate passes?

A stable sort guarantees that when two elements compare as equal under the current sort key, their original relative order is preserved rather than left unspecified. This matters directly for multi-key sorting done as a series of single-key sorts: sort by a secondary key first, then stably sort by the primary key, and elements sharing the same primary key retain their secondary-key order from the first pass, correctly producing a combined sort by (primary, secondary) without needing a single comparator that handles both keys at once. An unstable sort would silently scramble that secondary ordering among equal-primary-key elements.

Why does Python's sort achieve O(n) in the best case rather than always costing O(n log n) like a textbook mergesort?

Python uses Timsort, which specifically looks for and exploits "runs," contiguous stretches of already-ordered (or reverse-ordered) elements already present in the input, merging those runs rather than treating the data as uniformly random. Fully-sorted input is the extreme case of this: it's already one giant run, so Timsort recognizes it and finishes in linear time instead of doing the full comparison work a naive O(n log n) sort would perform regardless of input order. This is exactly why Timsort performs so well on real-world data, which is very often partially sorted already, not uniformly random.

An O(n^2) sort can be faster than an O(n log n) sort for small inputs. Why, and why doesn't that matter for a general-purpose sort function?

Big O describes asymptotic growth, not actual runtime, and O(n^2) algorithms often have smaller constant factors and simpler inner loops (no recursion or merge-buffer overhead) that make them genuinely faster in wall-clock time for small n, even though the more sophisticated O(n log n) algorithm would eventually win as n grows. This is exactly why production sort implementations, including Timsort, special-case small subarrays with a simple insertion sort internally rather than using the full merge-sort machinery on tiny inputs, getting the best of both regimes instead of picking one algorithm for every input size.

References

ShareXLinkedInReddit
Was this page helpful?
Suggest an improvement