Priority queues and binary heaps in Python
Use Python heapq to build a binary heap, retrieve priorities, keep equal-priority tasks in arrival order, and understand operation costs.
A priority queue retrieves the next item by priority. A binary heap is one implementation: it keeps the minimum or maximum at the root without sorting the whole array. Heap repair takes O(log n) work after adding an item or removing the root; reading the root takes O(1).
The valid min-heap [1, 4, 2, 7, 6, 3] puts 1 first, although 4 appears before 2. Remove 1 and repair the heap: 2 becomes the root. Repeating the removal gives increasing values; reading the array from left to right does not.
Suppose several tasks are waiting and you need the one with the smallest priority number next. You can keep a fully sorted list, but that maintains more ordering than this question needs. A priority queue provides the next item by priority; the binary heap below maintains just enough order to answer that question.
What a binary heap guarantees#
A binary heap has the shape of a complete binary tree: fill each level before the next, from left to right. Store it in an array. For a zero-based index k, the children are at 2k + 1 and 2k + 2 when those positions exist. No separate pointers are needed for these parent-child links.
In a min-heap, each parent is no greater than its children. Following that rule from any item back to the root shows why the root is a minimum. The rule does not order siblings. In [1, 4, 2, 7, 6, 3], the children of 1 are 4 and 2, and 4 appearing first does not violate it.
Watch the heap after removing its root
Min-heap array [1, 4, 2, 7, 6, 3] is valid but not sorted: 4 precedes 2. Predict what changes when the minimum is removed.
Root: 1. Heap array: [1, 4, 2, 7, 6, 3]. Read this array in order and you do not get priority order.
Enable JavaScript to change the trace. The initial worked result above remains readable.
A small array binary min-heap with distinct keys. Java iterator layout is unspecified; this array is one valid heap, not a promised Java traversal.
Remove the root in the experiment. The last item moves into the empty root position, then moves down by swapping with the smaller child until the rule holds again. This is called sifting down. Adding an item does the opposite: append at the next position and move it up while it is smaller than its parent. Equal keys are allowed.
A max-heap reverses the comparison and keeps a maximum at the root. These names describe the retrieval rule, not whether a large number means an urgent task. The application chooses that meaning. Python’s heapq documentation defines both invariants; Python 3.14 added the public heapify_max, heappush_max and heappop_max functions. The examples here use the older min-heap API and work on Python 3.9 and later.
Python heapq example#
This complete example heapifies an existing list, reads its minimum, inserts a new value and removes values in priority order. The input numbers are illustrative. The array’s internal layout is not the output contract; the popped values are.
heapify changes the list in place. Start with list(values) if the original must stay intact. Both heap[0] and heappop(heap) raise IndexError on an empty list, so check whether it contains anything first. Appending directly to a heap list can break its invariant; use heappush to maintain it.
Binary heap time complexity#
A complete binary tree has O(log n) height. Insertion repairs one path upwards; removing the root repairs one path downwards. Neither operation needs to sort every pair of items. The following bounds count heap work with constant-cost key comparisons; memory allocation and expensive comparison functions can add other costs.
| Operation | Binary-heap work | Reason |
|---|---|---|
| Read the root | O(1) | The next item is at index 0 |
| Push an item | O(log n) | Repair one upward path |
| Pop the root | O(log n) | Repair one downward path |
| Heapify n existing items | O(n) | Bottom-up repair; most nodes are near the leaves |
| Find an arbitrary item | O(n) | Heap order does not identify its array position |
| Pop every item | O(n log n) | Repeated removal produces a full ordered result |
Building with repeated pushes takes O(n log n) work in the worst case, while bottom-up heapify takes O(n). Nodes near the leaves need little or no downward repair; the few nodes near the root can travel farther. Princeton’s priority-queue material derives this linear construction bound. Storage for the heap is O(n); the full output list in our examples also takes O(n).
The neighbouring operations can have different costs in a library. Java SE 25’s PriorityQueue documents logarithmic enqueuing and dequeuing, constant-time peek, and linear contains(Object) and remove(Object). Removing the next item and locating a particular item are different tasks.
Keep equal priorities in arrival order#
For records, store a comparison key alongside the task. A pair (priority, task) has a trap: when priorities tie, Python compares the task values next. Two dictionaries do not have the ordering needed for that comparison.
Add a unique increasing counter between the priority and task. Earlier arrivals then win ties, and comparison never reaches the task object. This is an explicit policy you supply to the heap, as described in Python’s priority-queue implementation notes.
The urgent check comes first despite arriving second. Compile then precedes collect assets because their priorities tie and compile has the lower counter. The tasks and their numbers are illustrative; this function orders a finite batch and does not execute tasks, cancel them or provide thread coordination.
Iteration, updates and heap limits#
Iterating the backing array gives its current arrangement. Python’s heap invariant allows unsorted siblings; Java’s iterator explicitly offers no ordering guarantee. Repeatedly pop to consume values in priority order, or sort a copy with the same comparison rule for a full listing. Merely printing a heap does neither.
Do not change a stored comparison key and expect the heap to repair itself. Remove and reinsert using an appropriate interface, or push a fresh entry and identify old entries as stale when they are popped. Lazy deletion needs an identity or version check and can retain extra entries; the simple batch function above does not implement it. Python heapq has no public indexed decrease-key operation.
A heap is useful when the next minimum or maximum matters repeatedly. If you only need one minimum, a linear scan may be enough. If you need a fully ordered result once, sorting is often the clearer task. If you need repeated exact membership checks, a heap’s parent-child rule does not help locate arbitrary values; binary search explains lookup in an already sorted array. A linked list provides a different trade-off around positions and updates, rather than heap ordering.
For a dependency graph, priority can choose among tasks that are already eligible. It cannot make a blocked task safe to run. Topological sorting shows how unmet prerequisites decide the ready set before a queue or heap chooses its next member. Dijkstra’s shortest-path visualiser explores a different use of choosing by a changing distance estimate.
IF YOU REMEMBER ONE THING
A binary heap maintains the next winner. Choose the min or max rule, define how ties behave, and preserve the comparison keys. Expect ordered removal, not ordered array iteration.
Questions people also ask
5 QUESTIONSIs a priority queue the same as a binary heap?
A priority queue describes operations: add an item and retrieve the next one by priority. A binary heap is a common implementation of those operations. Other representations are possible, with different costs. Python heapq and Java PriorityQueue use heaps.
Why does printing a priority queue give an unsorted order?
The heap rule only orders parents relative to their children. It does not sort siblings or the entire backing array. Repeatedly removing the root gives priority order and consumes the heap; sort a copy if you need a full ordered listing without consuming it.
What does it cost to add and remove?
A binary heap repairs a path of O(log n) height after insertion or root removal. Reading the root takes O(1), heapifying an existing array takes O(n), and finding an arbitrary value generally takes O(n). These are operation bounds, not a benchmark or a guarantee for every priority-queue representation.
Can I change an item's priority after adding it?
Changing a stored comparison key can break the heap rule. Java PriorityQueue and Python heapq do not automatically rearrange entries when their objects change. Remove and reinsert through a suitable interface, or use fresh entries with a way to identify and skip stale ones.
Does a priority queue preserve arrival order for ties?
A binary heap does not inherently preserve arrival order. In Python, store (priority, increasing_counter, task) to make earlier equal-priority entries come out first. The unique counter also prevents Python from comparing task objects when priorities tie.