skip to content

In a min-heap sift-down, why must you swap the node with its smaller child, not its left child?

level: middleimportance: must knowfreq 62%

answer

  1. the promoted node becomes a parent of two
  2. which child ends up beneath the other?
  3. check the child you never compared
  4. the root can still look correct afterwards
  5. the last internal node may lack a right child

basics

~20 s

The promoted child becomes the parent of both children, so it must be the smaller one. Promote the left child while the right is smaller and the new parent exceeds its sibling — the property breaks below the swap, where nothing rechecks it.

solid answer

~50 s

Sift-down works by promoting a child over the sinking node, and the promoted child inherits *both* children of that position — including its former sibling. So the promoted key must be no larger than that sibling, which means it has to be the smaller of the two. Take the sinking key 25 above children 9 and 4: promote 9 and you get 9 sitting above 4, an immediate violation. What makes this bug nasty is that it is silent. The root can still end up holding the true minimum, so peek looks right and the next extraction looks right; the out-of-order key only surfaces several extractions later, far from the code that caused it. A correct sift-down does two comparisons per level: one to choose the smaller child, one to test it against the sinking key.

code

pseudocode · 10 lines
pseudocode
// a = min-heap over n live elements
// left(i), right(i) return child indexes; they do no bounds checks
sift_down(a, i, n):
  while left(i) < n:
    c = left(i)
    if a[c] < a[i]:
      swap(a[i], a[c])
      i = c
    else:
      break

go deeper

for a junior

Remember the rule as a sentence, not a line of code: in a min-heap you swap with the smaller child, in a max-heap with the larger. Know that a node may legitimately have only a left child.

for a middle

Be ready to trace a seven-element heap on a whiteboard and show where the wrong-child version breaks. Explain why the promoted node must dominate its former sibling, and count the two comparisons per level.

for a senior

Show that you know this failure is silent — the root can stay correct while a lower branch is wrong — and name the invariant assertion over all parent-child pairs as the test that catches it after a randomised operation sequence.

for a principal

Own the argument that hand-rolled structural code needs property-based verification rather than spot checks, and be able to say when writing a bespoke heap is justified at all versus taking a vetted one.

## What sift-down is actually doing Sift-down repairs a heap in which **exactly one** node may violate the invariant, and both of its subtrees are already valid heaps. That precondition is the whole reason a single root-to-leaf path is enough work: everything off the path was already correct and stays correct. The repair step is a promotion. The sinking node hands its position to one of its children, and that child now sits above *both* of the old children — its own subtree, and its former sibling's subtree. So the promotion must satisfy a specific condition: **the promoted key must be no larger than the sibling it is promoted over.** In a min-heap there is exactly one child that always satisfies that condition — the smaller one. ## The bug, traced on a triage board Take a triage board kept as a min-heap on priority score (lower is more urgent): ``` scores: [2, 3, 20, 9, 4, 30, 25] 2 / \ 3 20 / \ / \ 9 4 30 25 ``` Extract the top. Score 2 is returned, the last element (25) moves into the root, and the heap shrinks to six: ``` scores: [25, 3, 20, 9, 4, 30] ``` Now run the fragment attached to this question, the one that always takes the left child. - At the root: left child is 3. `3 < 25`, so swap. → `[3, 25, 20, 9, 4, 30]`, continue from position of 25. - There: left child is 9. `9 < 25`, so swap. → `[3, 9, 20, 25, 4, 30]`, continue. - No left child exists below → stop. Draw the result: ``` 3 / \ 9 20 / \ / 25 4 30 ``` Node 9 sits above node 4. The heap property is broken — and it is broken two levels down from where the sift began, on the branch the code never looked at. Run the correct version and 4 gets promoted instead, giving `[3, 4, 20, 9, 25, 30]`, a valid heap. ## Why it is a *silent* corruption Notice what did **not** go wrong: the root still holds 3, the true minimum. Peek returns the right answer. The next extract-min also returns the right answer. The board keeps looking healthy. The damage only becomes visible when the sift eventually reaches the corrupted region and a patient with score 9 is dispatched ahead of a patient with score 4 — an out-of-order dequeue with no stack trace and no obvious culprit. This is why "my extract returns the smallest key, so my heap is fine" is not a test. The only reliable check is a **property assertion over the whole structure**: for every internal position, the key is less than or equal to each existing child. That is a linear scan, cheap to run in a test after a randomised sequence of inserts and extracts, and it localises the violation immediately. ## The boundary the same loop must respect A complete tree has one awkward node: the last internal node may have a **left child but no right child**. That is not an exotic case — it happens for every heap with an even number of elements. So the child-selection step cannot blindly read the right child: 1. If the left child index is out of range, the node is a leaf → stop. 2. Otherwise start with the left child as the candidate. 3. **Only if** the right child index is in range, compare and take the right child if it is strictly smaller. 4. Swap with the candidate if the candidate is smaller than the sinking key; otherwise stop. Skipping step 3's range guard reads past the end of the live region — which, depending on how the backing storage is managed, either throws or, far worse, compares against stale data from an element that was already extracted. The stale-data variant produces exactly the same silent corruption as the wrong-child bug. ## Comparison budget Each level of a correct sift-down costs up to two key comparisons: one to pick the smaller child, one to decide whether to swap. Over a height of about `log2(n)` that is roughly `2·log2(n)` comparisons in the worst case — still O(log n), since constants do not change the class, but worth knowing when keys are expensive to compare (long identifiers, composite priority-then-timestamp tie-breakers). The buggy left-child version is *cheaper* per level, which is a useful reminder that a comparison count is not a correctness argument. ## The one-sentence version Promote the smaller child, because the promoted node becomes the parent of the other one — and check the right child exists before you look at it.

  • Where exactly does a correct sift-down loop stop, and what does a node with only a left child require?
    It stops on either of two conditions: the node has no left child (it is a leaf), or the node is already smaller than or equal to the smaller existing child. The one-child case is routine — it occurs for every even-sized heap at the last internal node — so the child-selection step must range-check the right child before reading it, otherwise it compares against stale or out-of-bounds data.
  • Would checking that extract-min still returns the smallest key catch this bug?
    Not reliably. In the traced example the root still holds the true minimum after the corruption, so the next extraction looks perfectly correct and the violation surfaces only several extractions later. The test that actually catches it is a full property assertion — walk every internal position and assert the key is no larger than each existing child — run after a randomised sequence of inserts and extracts.
  • Is there an equivalent wrong-child bug in sift-up?
    No, because sifting up offers no choice: a node has exactly one parent, so there is nothing to pick wrongly. Sift-up's characteristic bugs are different — running past the root instead of stopping there, or comparing in the wrong direction so a key rises when it should stay put. The asymmetry is worth naming: sift-down chooses, sift-up only compares.

saying these in an interview costs you the question

  • Swapping with the left child without comparing the right
  • Reading the right child without checking that it exists
  • Assuming a corrupted heap fails loudly on the next peek
  • Thinking one swap per extraction restores the property
  • Treating sift-down as repairing the whole structure, not one path

context