skip to content

Why is heapsort unstable, and can tie-breaking inside sift-down make it stable?

level: middleimportance: must knowfreq 62%

answer

  1. stability is about equal keys only
  2. which swap moves elements furthest?
  3. root trades places with the far end
  4. the array remembers no input order
  5. widen the key with the original index

basics

~20 s

Heapsort swaps the root with the far end of the heap, so equal keys lose their input order. No tie-break rule inside sift-down fixes it: heap positions carry no memory of where an element started.

solid answer

~50 s

Stability means equal keys keep their relative input order. Heapsort breaks it structurally, not accidentally. Every extraction swaps the root with the last slot of the heap, teleporting two elements that may have started far apart; after a few such swaps, the array positions of equal keys no longer reflect their original sequence. Tie-breaking in sift-down — say, always preferring the earlier child when two children compare equal — does not help, because the damage is done by the root-to-tail swap, and by then nothing in the array records which of two equal keys came first. A three-element trace is enough to show an inversion. The standard remedy is to widen the comparison key with each element's original index, which restores a total order with no ties at all; that costs O(n) extra space and therefore forfeits the in-place property that made heapsort attractive.

go deeper

for a junior

Know the definition — equal keys keep their input order — and that heapsort does not provide it. Be able to say what stability is for, such as sorting by one field after another.

for a middle

Explain the mechanism: the root-to-tail swap relates distant positions, so equal keys can invert, and a local tie rule inside sift-down cannot undo it.

for a senior

Show you can decide whether stability is even observable for the data at hand, and price the index-decoration workaround against just choosing a stable sort.

for a principal

Own the requirement question: whether downstream consumers depend on tie order at all, and whether making that dependency explicit in the comparison key beats depending on a sort's stability guarantee.

## What stability actually claims A sort is **stable** if elements with equal sort keys appear in the output in the same relative order they had in the input. The claim is about *equal keys only* — it says nothing about unequal ones — and it is meaningful only when equal-keyed elements are distinguishable, i.e. they carry payload or identity beyond the key. Sorting bare numbers with no attached data cannot observe stability at all, because you cannot tell which 5 is which. Where it matters: multi-pass sorting (sort by secondary key, then by primary key, and a stable primary pass preserves the secondary ordering for free), and any display or record ordering where ties should fall back to arrival order. ## The concrete inversion Take three records whose keys are 5, 5 and 1, tagged by input position so we can watch them: `5A, 5B, 1C`. - **Build.** The only internal node is index 0. Its children are `5B` and `1C`; the larger child is `5B`. The root `5A` compares equal to `5B`, and a sensible implementation does not swap on equality. The array stays `[5A, 5B, 1C]` and is a valid max-heap. So far, order preserved. - **Extract 1.** Swap root with the last heap slot: `[1C, 5B, 5A]`, heap shrinks to `a[0..1]`. Sift down: `1C` versus its only child `5B`; swap. Array is `[5B, 1C, 5A]`. - **Extract 2.** Swap root with the last heap slot: `[1C, 5B, 5A]`, heap shrinks to one element. Done. Output: `1C, 5B, 5A`. The input had `5A` before `5B`; the output has them reversed. Nothing about the implementation was careless — the equality tie was even resolved in favour of not moving. ## Why tie-breaking cannot rescue it The instinctive fix is to add a rule inside sift-down: on a tie between the two children, pick the left one; on a tie between parent and child, do not swap. Both rules are already the sensible defaults, and the trace above used them. They fail because they operate at the wrong moment. The order-destroying operation is the **root-to-tail swap**, which relates two array slots that have no relationship to input order — the element at the tail could have started anywhere. Sift-down's local tie rules can only choose between two children, and it has no way to know which of two equal keys entered the array first, because the implicit heap layout stores no such information. In other words: a comparison-based rule cannot recover data that the array does not contain. This is the general shape of the argument. Sorts that move elements only between adjacent or contiguous positions — insertion-style shifts, a merge that takes from the left run when the fronts tie — can be stable by choosing correctly at each local decision. Sorts built on long-range swaps (heapsort, and typical in-place partitioning schemes) cannot, because a single swap can invert an arbitrary pair. ## The standard remedy and what it costs Make the ties disappear. Pair each element with its original index and compare `(key, index)` lexicographically. Now no two elements compare equal, the sort's order is fully determined, and equal keys necessarily come out in input order. This is universal — it stabilises *any* correct sort — and it has a price: - an extra index per element, so O(n) additional space, which forfeits the in-place property that was heapsort's main selling point; - a wider, slower comparison; - a decorate/undecorate pass, or an indirection layer, either of which hurts locality further. At that point, if stability is a requirement, you should be asking whether an intrinsically stable O(n log n) sort is the better answer, and accepting its O(n) scratch buffer honestly rather than paying the same space to bolt stability onto a sort that resists it. Mainstream runtimes have split on exactly this: several ship a stable adaptive merge-based sort (Timsort and its variants) as the default for object references while using an unstable introsort-family algorithm for primitive values where stability is unobservable — a design choice you can see in both Java's and Python's standard sorting stories, which reached the same conclusion by different routes. ## What to say in an interview State the definition, state that heapsort is unstable, and immediately give the *reason* — long-range swaps, no positional memory — rather than just the label. Then note that stability is unobservable for keys with no payload, and that index-decoration is the general escape hatch with an O(n) space bill. That sequence answers the question and the two follow-ups an interviewer was about to ask.

  • When does heapsort's instability cost you nothing at all?
    When equal keys are indistinguishable — sorting bare numeric values with no attached payload, for instance. Stability is only observable if two elements can compare equal on the key while differing in something you can see. It also costs nothing when the key is unique by construction, since then no ties exist.
  • How would you get a stable ordering while still using heapsort?
    Decorate each element with its original index and compare key first, index second. That removes all ties, so the output is forced into input order among equal keys, and it works for any correct sort. The bill is O(n) extra storage plus a wider comparison, which sacrifices the in-place property — usually the reason you would rather pick an intrinsically stable sort instead.
  • Why can a merge-based sort be stable when heapsort cannot?
    Its element movement is local and ordered: when the fronts of two runs compare equal, taking from the left run preserves input order, and elements never jump across the array arbitrarily. Every ordering decision is a local choice between two candidates whose relative input order is known, so a consistent tie rule is enough.

saying these in an interview costs you the question

  • Says careful tie-breaking in sift-down makes it stable
  • Claims stability means equal elements are adjacent
  • Thinks stability matters even for bare keys with no payload
  • Believes heapsort is stable because the heap is ordered
  • Offers index decoration without mentioning its space cost

context