Skip to content

Intermediate: core algorithms · Heaps and greedy algorithms · topic 36 of 61 · 40 min

Heaps and priority queues

Explanation

A priority queue always hands out the most important item first, not the oldest. A binary heap is the usual way to build one: a complete binary tree stored in an array, where every parent is smaller than (or equal to) its children. Push and pop cost O(log n), looking at the smallest item costs O(1), and turning a whole list into a heap costs only O(n). Python's heapq module gives you a min-heap on a plain list.

Why a priority queue

A normal queue serves people in arrival order. Many real systems need a different rule: serve the MOST URGENT first. A hospital emergency desk treats a heart attack before a sprained ankle, even if the ankle arrived first. A ride-hailing app offers a trip to the nearest free driver. An operating system runs high-priority jobs first. A data structure that always gives you the item with the best priority is called a priority queue.

You could keep a sorted list, but inserting into it costs O(n) because items shift. You could keep an unsorted list, but finding the best item costs O(n). A heap does both jobs in O(log n).

What a heap is

A binary heap is a complete binary tree (every level full except maybe the last, which fills from the left; see Module 7) stored in an array. The node at index i has children at 2i + 1 and 2i + 2 and its parent at (i − 1) // 2. In a min-heap, every parent is less than or equal to its children; this is the heap property. So the smallest item is always at index 0, the root. A max-heap is the mirror: every parent is greater than or equal to its children.

A heap is NOT sorted. Only the parent-child rule holds; two siblings, or two nodes on different branches, can be in any order. That weaker rule is exactly what makes updates cheap.

OperationHow it worksCost
peek (look at the smallest)Read heap[0]O(1)
pushAppend at the end, then "sift up": swap with the parent while smaller than itO(log n)
pop (remove the smallest)Move the last item to the root, then "sift down": swap with the smaller child while bigger than itO(log n)
heapify (build from a list)Sift down every non-leaf node, from the last one back to the rootO(n)

Push and pop walk one path between the root and a leaf, and a complete tree has height about log₂ n, so they are O(log n). Heapify is O(n), not O(n log n), because most nodes sit near the bottom and can only move a step or two down; the few nodes near the top that can move far are rare. That makes heapify cheaper than pushing items one by one.

python
def push(heap, x):
    heap.append(x)
    i = len(heap) - 1
    while i > 0:
        parent = (i - 1) // 2
        if heap[parent] <= heap[i]:
            break                      # the heap property holds again
        heap[parent], heap[i] = heap[i], heap[parent]
        i = parent

h = []
for x in [5, 1, 8, 3, 2]:
    push(h, x)
print(h)   # Output: [1, 2, 8, 5, 3]
Sift up by hand. The list is a valid min-heap but not sorted: 8 comes before 5 and 3.

Python heapq

The heapq module turns a plain list into a min-heap. heappush(h, x) and heappop(h) are O(log n); h[0] is the smallest item; heapify(lst) rearranges a list in place in O(n). heapq.nlargest(k, items) and heapq.nsmallest(k, items) return the top k directly.

python
import heapq

h = []
for x in [5, 1, 8, 3, 2]:
    heapq.heappush(h, x)
print(h, h[0])                            # Output: [1, 2, 8, 5, 3] 1
print(heapq.heappop(h), heapq.heappop(h))  # Output: 1 2
nums = [9, 4, 7, 1, 8]
heapq.heapify(nums)
print(nums)                               # Output: [1, 4, 7, 9, 8]

Max-heaps: negate the values

heapq only gives a min-heap. The usual trick for a max-heap is to push −x and negate again when you read: the smallest negative is the largest original number. (Python 3.14 added max-heap functions such as heapq.heappush_max, but many online judges and the browser runner here use older versions, so learn the negation trick.)

python
mx = []
for x in [5, 1, 8, 3]:
    heapq.heappush(mx, -x)
print(-mx[0], -heapq.heappop(mx))   # Output: 8 8

Priorities with tuples and tie-breakers

To store items with a priority, push tuples (priority, item). Tuples compare item by item, so the priority decides first. If two priorities are equal, Python compares the next item, which can be a surprise: strings compare alphabetically, and dicts or custom objects cannot be compared at all.

python
tasks = []
heapq.heappush(tasks, (2, "write report"))
heapq.heappush(tasks, (1, "fix outage"))
heapq.heappush(tasks, (2, "answer email"))
print([heapq.heappop(tasks)[1] for _ in range(3)])
# Output: ['fix outage', 'answer email', 'write report']

q = []
heapq.heappush(q, (1, {"id": 7}))
heapq.heappush(q, (1, {"id": 3}))
# TypeError: '<' not supported between instances of 'dict' and 'dict'
Equal priorities fall back to comparing the second item. Add a counter to break ties safely.

The fix is a tie-breaker: push (priority, counter, item), where counter increases with every push. Equal priorities are then served in arrival order (first come, first served), and the item itself is never compared.

Common mistakes

  • Thinking the heap list is sorted and reading h[1] as the second smallest. Fix: only h[0] is special; pop to get items in order.
  • Forgetting that heapq is a min-heap. Fix: negate values for a max-heap, and negate back when you read.
  • Pushing (priority, item) where items are dicts or objects. Fix: (priority, counter, item).
  • Using sorted() on every insert to keep a priority list. That is O(n log n) per insert. Fix: heappush, O(log n).
  • Popping from an empty heap: heapq.heappop([]) raises IndexError. Fix: check if h: first.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Heaps and priority queues · 1/8

Heaps and priority queues

This lesson introduces heaps, the data structure behind priority queues.

Prefer a video by a teacher? Find free videos on “binary heap priority queue sift up sift down heapify python heapq explained” on YouTube ↗

In real life: Emergency triage in a hospital

In a hospital emergency department, a triage nurse gives each patient a severity level. Patients are treated by severity, and among equal severities by arrival time. A priority queue models this exactly: push (−severity, arrival number, name) so that higher severity comes first and ties go to whoever came first. Ride-hailing apps use the same idea when they pick the nearest available driver for a trip.

python
import heapq

queue = []
arrivals = [("Anil", 2), ("Bina", 5), ("Chetan", 3), ("Divya", 5)]
for order, (name, severity) in enumerate(arrivals):
    heapq.heappush(queue, (-severity, order, name))
while queue:
    print(heapq.heappop(queue)[2], end=" ")
Shows Bina Divya Chetan Anil: severity 5 first (Bina came before Divya), then 3, then 2.

Practice (0/4 done)

Task 1 · Code · Python

Write heap_sort(nums) that returns a NEW list with the values of nums in increasing order, using heapq (heapify a copy, then pop everything). Do not change nums and do not call sorted() or .sort(). Example: heap_sort([5, 1, 8, 3, 2]) returns [1, 2, 3, 5, 8].

Task 2 · Code · Python

Build a class MinHeap WITHOUT using heapq. Methods: push(x) adds x; pop() removes and returns the smallest item; peek() returns the smallest item without removing it; size() returns the number of items. pop() and peek() on an empty heap must raise IndexError. Store the heap in a list (children of i at 2i + 1 and 2i + 2) so push and pop are O(log n).

Task 3 · Code · Python

You have stones with positive weights. Each turn, take the two HEAVIEST stones x ≥ y and smash them: if x == y both are destroyed; otherwise a stone of weight x − y remains. Repeat until at most one stone is left. Write last_stone_weight(stones) that returns the weight of the last stone, or 0 if none is left. Use heapq as a max-heap (negate values). Example: last_stone_weight([2, 7, 4, 1, 8, 1]) returns 1.

Task 4 · Code · Python

Patients arrive in the order of the list patients, each as (name, severity). Higher severity is treated first; equal severities are treated in arrival order. Write triage_order(patients) that returns the names in treatment order. Use heapq with tuples (-severity, arrival_number, name). Example: triage_order([("Anil", 2), ("Bina", 5), ("Chetan", 3), ("Divya", 5)]) returns ["Bina", "Divya", "Chetan", "Anil"].

Quick quiz

Question 1

1.In a min-heap stored as a list h, which statement is always true?

Question 2

2.What is the cost of heapq.heapify on a list of n items?

Question 3

3.How do you usually get a max-heap from Python's heapq on Python 3.12?

Question 4

4.Why push (priority, counter, item) instead of (priority, item)?

Question 5

5.Pop on a binary heap moves the last item to the root and then…

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

In a min-heap stored as a list h, which statement is always true?

Answer: h[0] is the smallest item

Only the parent-child rule holds, so the root is the minimum. The rest is not in sorted order.

What is the cost of heapq.heapify on a list of n items?

Answer: O(n)

Bottom-up sift-down is linear, because most nodes are near the bottom and move only a little.

How do you usually get a max-heap from Python's heapq on Python 3.12?

Answer: Push -x and negate again when reading

heapq is a min-heap; negating flips the order. (Python 3.14 adds _max functions, but older versions do not have them.)

Why push (priority, counter, item) instead of (priority, item)?

Answer: Equal priorities then compare the counter, so items are never compared and arrival order is kept

Without the counter, a tie compares the items, which may raise TypeError or give an unwanted order.

Pop on a binary heap moves the last item to the root and then…

Answer: sifts it down, swapping with the smaller child while it is bigger

The moved item may be too big for the root, so it swaps downward with its smaller child until the heap property holds.

What does heapq.heappop(h) return?Related

Answer: The smallest item

heapq is a min-heap: the smallest item is always at the front and comes out first.

From Stacks and queues

In the two-heap median, what does the max-heap "low" hold?Related

Answer: The smaller half of the numbers

low keeps the smaller half so its top is the largest of the small values, one of the middle values.

From Heap patterns: top-k and streams

std::priority_queue<int> pq; after pushing 4, 9 and 2, what is pq.top()?Related

Answer: 9

By default priority_queue is a max-heap.

From set, stack, queue, deque and priority_queue

Which statement about a complete binary tree is true?Related

Answer: Every level is full except maybe the last, which fills from the left

That is the definition of complete. The first is "full", the second "perfect", and the last is a BST rule.

From Trees: words and shapes

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 4 more topics in “Heaps and greedy algorithms” before its exam: Heap patterns: top-k and streams, Greedy algorithms: the idea, Interval problems, More greedy classics.
  2. Module 8 exam: Heaps and greedy algorithms · 15 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Graphs. 61 topics left in the course before the final exam.

← Previous: Lowest common ancestor, serialisation and balanced trees