Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Sorting at Scale  ›  Lesson

Heap Sort

Heap Sort 24 minBuild a max-heap in the array itself, then repeatedly extract the root into the shrinking sorted suffix
You're building a piece ofQuarry — the ingest sorter
This piece — heap_sort(): Ranks a batch of tickets by priority in place, no extra memory.
Scenario A batch of support tickets lands overnight, and the morning queue needs them ranked by priority before anyone opens the dashboard.
Your task
Build heap_sort(arr). Return a NEW list with arr's values in ascending order, using an explicit max-heap: build the heap bottom-up with sift_down, then repeatedly swap the root with the last unsorted slot and sift_down the shrinking heap. No sorted() or heapq. O(n log n) time, O(1) extra space. Example: heap_sort([5, 1, 9, 3, 7]) -> [1, 3, 5, 7, 9].

Heap Sort

In short: heap sort turns the array itself into a heap, using no extra memory. First it builds a max-heap bottom-up, so the biggest value sits at index 0. Then it repeats one move: swap the root with the last unsorted slot, shrink the heap by one, and sift the new root down to fix the one violation that swap created. Each sift-down costs O(log n), and there are n of them, so the whole sort is O(n log n) time and O(1) extra space — but it is not stable: equal keys can come out in a different order than they went in.

An array of six boxes with curved roof lines connecting index 0 to its children at indices 1 and 2, and index 1 to its children at indices 3 and 4, like a tree drawn flat onto the row it lives in

Rank a batch of support tickets

Quarry's nightly job ranks a batch of support tickets by priority before anyone reads them, highest first. The tickets already sit in one array — no separate structure needed:

python

heap_sort builds ascending order, because each extraction moves the current biggest value to the end. For "highest priority first", read the result backwards, or flip every comparison to build a min-heap instead.

Watch the heap get built, then watch two extractions shrink it from the right:

watch it move · array

Building the heap only fixes parent-child violations, working from the last parent up to the root — most of the array never moves.

Start at the last parent, index 1 (10). Its only child is index 3 (5) — no violation there. 5 cells. At index 1.

1 / 5
watch it move · array

Extraction swaps the root into the shrinking heap's last slot, then sifts the new root back down. The sorted suffix on the right only ever grows.

This is already a valid max-heap: 9 beats both of its children, 5 and 7. 5 cells. At index 0.

1 / 7

Why heap sort needs no second array

Merge sort (section 3) is O(n log n) too, but it allocates a new array on every merge — O(n) extra memory at its peak. Heap sort's whole trick is that the shape of a complete binary tree has no gaps, so parent, left and right are just arithmetic on one list. The "heap" and the "array" are the same object:

What does this print?
heap = [10, 8, 9, 3, 7, 1]   # already a valid max-heap
print(heap[0] == max(heap))

That is also why the last loop starts at n - 1 and counts down: the sorted answer is built from the back of the array forward, one confirmed maximum at a time, while the unsorted front keeps shrinking.

Heap sort scrambles ties

Heap sort compares whatever key you give it, and a sift-down swap does not care whether two items are "equal enough to leave alone". Sort these tickets by priority only, and watch the two priority-2 tickets — and the two priority-5 tickets — change order, with no bug anywhere:

python

C comes out before A, and D before B — reversed from the order they arrived in, even though this code has nothing wrong with it. Building the heap moves items by position in the tree, not by their place in the original array, so two equal keys can end up on opposite sides of a swap for reasons that have nothing to do with their value. No comparison-based rule fixes this without extra bookkeeping (pairing every key with its original index, which costs memory). If order among equal keys matters, carry the index alongside the key, or pick a stable sort like merge sort instead.

Quick practice

What does this print?
heap = [50, 40, 45, 10, 30, 20]
print(heap[1] > heap[3], heap[1] > heap[4])
What does this print?
def sift_down(heap, i, size):
    while True:
        left, right = 2 * i + 1, 2 * i + 2
        biggest = i
        if left < size and heap[left] > heap[biggest]:
            biggest = left
        if right < size and heap[right] > heap[biggest]:
            biggest = right
        if biggest == i:
            return
        heap[i], heap[biggest] = heap[biggest], heap[i]
        i = biggest

heap = [1, 12, 9, 4, 8]
sift_down(heap, 0, len(heap))
print(heap)
broken — fix it

The two lines inside the extraction loop are in the wrong order. Swap them.


🎯 Your turn

Build heap_sort(arr): return a new list with arr's values in ascending order, using an explicit max-heap (no sorted(), no heapq).

  • heap_sort([5, 1, 9, 3, 7]) → [1, 3, 5, 7, 9]
  • heap_sort([]) → []

Hint: build the max-heap bottom-up from n // 2 - 1 down to 0, then for end from n - 1 down to 1: swap heap[0] and heap[end], then sift_down(0, end) so the shrinking heap stays valid.

Press ▶ Run, try the Live App chips, then ✓ Check.

Practice — level up

0/5 solved

Solve each in its own editor — Run to try, Check to grade. Stuck? Reveal a Hint, or the full Solution + walkthrough.

Warm-upIs this already a heap?

Write is_max_heap(arr), returning True if every index's value is at least as big as both its children's, treating arr as a complete binary tree stored level by level. is_max_heap([9, 8, 8, 5, 7, 2, 1]) -> True.

heaps
DrillOne sift-down pass

Write sift_down(heap, i, size), fixing ONE heap-property violation starting at index i, only looking at indices below size. Return the resulting list. sift_down([2, 8, 5, 12, 9, 7], 0, 6) -> [8, 12, 5, 2, 9, 7].

heapssift-down
BuildBuild the heap, bottom-up

Write build_max_heap(arr), turning arr into a valid max-heap in O(n) by calling sift-down on every parent from the last one up to the root. Return the resulting list. build_max_heap([4, 1, 3, 2, 16, 9, 10, 14, 8, 7]) -> [16, 14, 10, 8, 7, 9, 3, 2, 4, 1].

heapsarrays
BossSort descending with a min-heap

Write heap_sort_desc(arr), returning a new list sorted descending, by building a min-heap instead of a max-heap (flip every comparison). heap_sort_desc([5, 3, 8, 1]) -> [8, 5, 3, 1].

heapssorting
CapstoneStream in, keep only the top k

Write k_largest(arr, k), returning the k largest values from arr in descending order, using a min-heap of size at most k so you never hold more than k items at once. If k <= 0, return []. k_largest([5, 3, 8, 1, 9, 2], 3) -> [9, 8, 5].

heapsgreedy
heap_sort.py
Call the function you wrote — just like your app's frontend would. Edit the inputs and hit Call.
heap_sort(arr) → listA new list with arr's values sorted ascending, built with an explicit max-heap.
try:
returned
Press Run to execute your code.
Press Check to run the tests for this lesson.