datarekha

Heaps & Priority Queues

How a heap keeps the best item always on top — O(1) peek, O(log n) push and pop — and why it is the natural priority queue for Dijkstra, beam search, and streaming top-K.

8 min read Intermediate Data Structures & Algorithms Lesson 15 of 32

What you'll learn

  • How a heap stores a binary tree in a plain array using the index rule i → 2i+1, 2i+2
  • What the heap property means, and how sift-up and sift-down restore it in O(log n)
  • Why building a heap from scratch is O(n), not O(n log n)
  • How a size-K heap finds the K largest items in a stream in O(n log K)

Before you start

A heap answers one question, over and over, very cheaply: what is the best item right now?

Think of a hospital triage desk. The most urgent patient is always seen next, no matter who arrived when. A new arrival is slotted in according to how urgent they are, not pushed to the back; and when the most urgent is taken in, the desk settles on the next-most-urgent in a moment, without anyone scanning the whole waiting room. A task scheduler, a shortest-path search, a “top 10 results” buffer — all of them are that same desk. The structure behind it is the heap.

A tree that lives in an array

A heap is a binary tree with a strict rule, but it carries no pointers at all — it lives inside a plain array, and the tree shape comes purely from arithmetic on the indices:

  • the node at index i has its children at 2i + 1 and 2i + 2;
  • and so its parent is at (i − 1) // 2.

Because the tree is always filled level by level, left to right, with no gaps, the array stays dense and cache-friendly.

136598136598012345root’s children:2×0+1, 2×0+2
The same heap, two views. Index 0 is the root; its children are at indices 1 and 2, theirs at 3, 4 and 5, 6.

The heap property

The rule is wonderfully local. In a min-heap, every parent is both of its children — and nothing more is required. It says nothing about siblings or cousins; only the parent-child line matters. From that one local rule a global fact follows for free: the smallest value in the whole heap can only be the root, at index 0. (A max-heap is the same with , putting the largest at the root.) That is why “what is the best item right now?” is just reading index 0 — O(1).

Keeping the property: sift-up and sift-down

When you push a new value, you drop it at the end of the array and let it sift up: compare it with its parent, and if it is smaller (in a min-heap), swap, then repeat from the parent’s spot. Each swap climbs one level, and a complete tree of n nodes is only ⌊log₂ n⌋ levels tall — so a push is O(log n).

When you pop the best item, you take the root as your answer, move the last element into the root spot, and let it sift down: compare it with its children, swap it with the smaller one if needed, and repeat. Again one root-to-leaf path at most — O(log n).

A heap is a priority queue

This is the standard way to build a priority queue — a collection that always serves the highest- (or lowest-) priority item next. Push O(log n), pop O(log n), peek O(1). Python’s heapq module is precisely this, always as a min-heap; for a max-heap you negate the values going in and out.

import heapq

h = []
for val in [34, 12, 78, 5, 56, 23, 91]:
    heapq.heappush(h, val)
print("smallest right now:", h[0])

print("popped in order:", [heapq.heappop(h) for _ in range(len(h))])
smallest right now: 5
popped in order: [5, 12, 23, 34, 56, 78, 91]

Popping repeatedly hands the items back smallest-first — the heap never sorted the whole array, it just kept the minimum cheap to reach each time.

The streaming top-K trick

Here is the pattern that makes heaps indispensable in data work. Given a stream of n numbers, find the K largest. Sorting everything is O(n log n). A heap does better: keep a min-heap of size K, and for each new number, if it beats the heap’s current minimum, replace that minimum; otherwise ignore it. The heap always holds the K largest seen so far, and each step costs O(log K).

import heapq

def top_k(nums, k):
    heap = []
    for x in nums:
        if len(heap) < k:
            heapq.heappush(heap, x)
        elif x > heap[0]:               # beats the smallest of our current best K
            heapq.heapreplace(heap, x)  # pop the min, push x — one O(log K) step
    return sorted(heap, reverse=True)

data = [41, 7, 93, 55, 28, 76, 13, 62, 84, 37]
print(top_k(data, 3))
print(heapq.nlargest(3, data))         # the stdlib does the same thing
[93, 84, 76]
[93, 84, 76]

The whole pass is O(n log K). When K is much smaller than n — the 10 largest out of ten million — that is close to a single linear scan, and far cheaper than sorting the lot.

Practice

Quick check

0/3
Q1A node sits at index 6 in a heap's backing array. Where is its left child?
Q2You call heappop() on a min-heap. What is the cost, and what comes back?
Q3You need the 10 largest values from a stream of 10 million numbers. Which approach is most efficient?

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions

Related lessons

Explore further

Skip to content