Heapsort sorts in place — where does the heap live, and how does the array change as it runs?
answer
- no second array is ever allocated
- children of i live at 2i+1, 2i+2
- two regions separated by a moving boundary
- root swaps with the last heap slot
- heap shrinks, sorted tail grows
basics
~20 sHeapsort builds its max-heap inside the input array itself — nothing extra is allocated. The array splits into a heap prefix and a growing sorted suffix; each round swaps the root to the boundary and shrinks the heap by one.
solid answer
~40 sThere is no separate heap object. A binary heap of n items is stored implicitly in the array: the node at index `i` has children at `2i+1` and `2i+2`, so the tree structure is pure index arithmetic. Heapsort runs in two phases. First it rearranges the whole array into a max-heap in linear time. Then it repeats n-1 times: swap `a[0]` (the current maximum) with the last slot still inside the heap, shrink the heap boundary by one, and sift the new root back down over the smaller heap. The invariant is that `a[0..end]` is always a valid max-heap and `a[end+1..n-1]` already holds the largest elements in final sorted order. Because every move is an in-array swap, auxiliary space is O(1), and total time is O(n log n) in the worst case.
go deeper
Be ready to state the two phases, the index arithmetic for children, and that the sort happens inside the input array with O(1) extra space and O(n log n) time.
Explain the loop invariant out loud: heap prefix, sorted suffix, boundary moving left one slot per extraction, and why the loop can stop with one element left.
Show you know what the in-place guarantee buys in production — no allocation, no mid-sort out-of-memory failure — and that it says nothing about wall-clock speed.
Frame in-place as a memory-risk decision: which workloads justify forfeiting adaptivity and speed for a sort that provably allocates nothing, and how you would state that requirement.
## The heap is the array The thing that surprises people first about heapsort is that no heap is ever *built* as a separate data structure. A complete binary tree can be laid out level by level in a flat array, and then the parent/child links are arithmetic rather than pointers: - children of index `i` are `2i + 1` and `2i + 2` (0-based) - parent of index `i` is `(i - 1) / 2`, integer division - an array of length `size` has internal nodes at indices `0 .. size/2 - 1`; everything past that is a leaf Because the tree is *complete* (every level full except possibly the last, which fills left to right), there are no gaps, so the array wastes nothing on structure. This implicit representation is the entire reason heapsort can sort with O(1) auxiliary space while a merge-based sort in its standard array form needs an O(n) scratch buffer. ## Two phases, one array **Phase 1 — build.** Rearrange the input into a max-heap: every node is greater than or equal to its children, so the global maximum sits at index 0. Done bottom-up, this phase costs O(n), not O(n log n) — the linear bound has its own derivation and is a heap-mechanics topic in its own right. **Phase 2 — extract.** Maintain a boundary `end` that marks the last slot still belonging to the heap. Repeat until one element is left: 1. `swap(a[0], a[end])` — the maximum of the remaining heap moves to the slot it will occupy forever. 2. `end = end - 1` — the heap shrinks by one; the sorted region grows by one. 3. Sift the new root down **over the shrunken heap only**, restoring the heap property in O(log size). At the top of every iteration the array is two disjoint regions with a hard invariant: ``` [ 0 .............. end ][ end+1 ......... n-1 ] max-heap, unsorted sorted, final, and every element here is >= every element there ``` When the heap region shrinks to a single element, that element is the smallest, and it is already at index 0 — where it belongs. So the loop stops at `end == 0`; there is nothing left to place. ## Why the output comes out ascending from a max-heap This trips people up: a max-heap gives you the *largest* element first, yet the result is ascending. That is exactly because the largest element is written to the *back* of the array and the region behind the boundary is filled right to left. If you want descending order in place, you build a min-heap instead and the same machinery fills the tail with the smallest elements. There is no reversal step in either direction. ## The cost sheet - Build: O(n). - Extract loop: n-1 sift-downs, each O(log n) → O(n log n). - Total: O(n log n) **worst case**, and also O(n log n) best case — heapsort is not adaptive, so already-sorted input buys you essentially nothing. That flatness is a feature when you need a predictable bound and a drawback when your data is usually nearly ordered. - Auxiliary space: O(1) for the standard iterative form. Note the qualifier: if sift-down is written recursively, the call stack is O(log n), and recursion depth is genuinely part of space complexity — it just does not change the practical story here. ## What "in place" actually promises In place means O(1) auxiliary space (or O(log n) for recursion), not "never moves anything" and not "cache-friendly". Heapsort touches the array with long-range swaps, which is precisely why it can be slower in wall-clock terms than a sort with the same asymptotics but sequential access. In place is a *memory* guarantee, not a *speed* guarantee — a distinction interviewers like to poke at. One more consequence worth internalising: because the algorithm allocates nothing at all, it cannot fail partway through for lack of memory. On systems where an allocation failure mid-sort is unacceptable, that property matters more than a constant factor of speed.
- If a max-heap yields the largest element first, why is the output in ascending order?Because the extracted maximum is written to the back of the array, not the front. Each round swaps the root into the last slot of the current heap region and then shrinks that region, so the sorted tail fills from right to left with progressively smaller values. Build a min-heap instead and the same loop produces descending order — no reversal pass is needed either way.
- Is heapsort's auxiliary space really O(1)? Where could hidden space creep in?In the standard iterative form, yes — every move is a swap inside the input array. The one place space creeps in is a recursive sift-down, whose call stack is O(log n) deep; recursion depth counts as space. Writing sift-down as a loop removes even that, which is why implementations aimed at constrained environments always iterate.
- Does heapsort run faster on input that is already sorted?Essentially no. Heapsort is non-adaptive: the build phase still restructures the array, and every extraction still sifts a small element down a path near the full height. Best, average and worst case are all O(n log n). If you expect nearly-ordered input and want to exploit it, an adaptive merge-insertion hybrid is the family that pays off there.
Think of a single tray of cards where the left part is a tournament bracket and the right part is the finished podium: each round the winner steps out of the bracket onto the podium, and the bracket gets one card smaller.
saying these in an interview costs you the question
- Says heapsort needs O(n) space for a separate heap
- Claims a min-heap is required for ascending order
- Thinks a reversal pass runs at the end
- Says already-sorted input makes heapsort O(n)
- Confuses in-place with cache-friendly