Python heapq is one of those standard library gems that most beginners walk right past — until the day they need to grab the smallest item from a large dataset without sorting the entire thing. That is exactly where the heapq module earns its keep. It gives you a heap data structure built directly on top of a regular Python list, and it does it efficiently without any external dependencies. By the end of this guide you will know how to use python heapq to push and pop elements, find the N largest or smallest values, merge sorted streams, and build a working priority queue — all using only what Python ships with.

What Is a Heap and Why python heapq Uses One

Before touching any code it helps to understand what a heap actually is. A heap is a specialized binary tree that enforces one important rule: every parent node must be smaller than or equal to both of its children. That specific flavor is called a min heap, and it is exactly what python heapq gives you by default.

What makes heaps powerful is that the smallest element always sits at the top — index zero in a list. You can read it in constant O(1) time, and inserting or removing an element takes O(log n) time. Compare that to sorting a list every time you want the minimum, which costs O(n log n), and the value of the heap data structure becomes obvious pretty quickly.

Python's heapq module implements this binary heap python-style directly on a plain list. There is no special heap object or class to instantiate. The functions from the heapq module python library operate on an ordinary list and maintain the heap invariant for you automatically behind the scenes.

heapify — Turning Any List Into a Heap

The first function most people reach for is heapq.heapify(). It takes an ordinary unsorted list and rearranges it in-place so that it satisfies the heap property. Python heapify runs in O(n) time, which is faster than inserting elements one at a time.

import heapq

scores = [42, 15, 8, 73, 5, 29, 61]
heapq.heapify(scores)
print(scores)
[5, 15, 8, 73, 42, 29, 61]

The list is not fully sorted after python heapify — and that is by design. A heap only guarantees that the first element is the minimum. The internal layout follows a binary heap python structure where each parent at index i is smaller than its children at 2i+1 and 2i+2. You do not need to think about that layout yourself; python heapq manages it invisibly every time you call a heapq function.

heappush — Adding New Elements Safely

Once you have a heap you can add new elements using heapq.heappush(). It appends the value and then restores the heap property by bubbling the new element up to its correct position in O(log n) time.

import heapq

tasks = []
heapq.heappush(tasks, 10)
heapq.heappush(tasks, 3)
heapq.heappush(tasks, 7)
heapq.heappush(tasks, 1)
heapq.heappush(tasks, 5)

print(tasks)
[1, 3, 7, 10, 5]

Notice that 1 landed at index zero because it is the smallest value pushed so far. Every call to heappush in python heapq guarantees the minimum stays at the front. This is far more efficient than appending and re-sorting when you are streaming in data item by item.

heappop — Removing the Smallest Element

heapq.heappop() removes and returns the smallest element from the heap. After removing it, the function reorganizes the remaining elements to restore the heap invariant — also in O(log n) time.

import heapq

readings = [1, 3, 7, 10, 5]
heapq.heapify(readings)

smallest = heapq.heappop(readings)
print("Popped:", smallest)
print("Remaining heap:", readings)
Popped: 1
Remaining heap: [3, 5, 7, 10]

After popping 1, the next smallest value 3 moved to the front automatically. This heappush heappop combination is the backbone of any python priority queue — you push items with their priority attached and pop them out in ascending priority order without ever manually sorting anything.

heapq nlargest and nsmallest — Finding the Top N Items

Sometimes you do not want just the single smallest item — you want the five cheapest flights, the ten highest-scoring users, or the three most recent log entries. That is exactly where heapq.nlargest() and heapq.nsmallest() shine. These are two of the most practically useful functions in the heapq module python provides, and they work without sorting the entire collection.

import heapq

product_prices = [149.99, 29.99, 9.99, 299.99, 59.99, 19.99, 499.99, 79.99]

cheapest_three = heapq.nsmallest(3, product_prices)
most_expensive_two = heapq.nlargest(2, product_prices)

print("3 cheapest:", cheapest_three)
print("2 most expensive:", most_expensive_two)
3 cheapest: [9.99, 19.99, 29.99]
2 most expensive: [499.99, 299.99]

Both functions also accept a key argument, which makes heapq nlargest nsmallest extremely flexible when working with dictionaries or custom objects:

import heapq

employees = [
 {"name": "Alice", "salary": 72000},
 {"name": "Bob", "salary": 95000},
 {"name": "Carol", "salary": 61000},
 {"name": "Dave", "salary": 88000},
]

top_earners = heapq.nlargest(2, employees, key=lambda e: e["salary"])
for emp in top_earners:
 print(emp["name"], "—", emp["salary"])
Bob — 95000
Dave — 88000

The key function works just like Python's built-in sorted() key — python heapq uses it only for comparison and never modifies your original data.

heapreplace and heappushpop — Atomic Swap Operations

Python heapq provides two functions that combine a push and a pop into a single efficient atomic operation. Knowing when to use each one saves you both code and time.

heapq.heapreplace(heap, item) pops the smallest element and pushes the new one in a single pass — faster than calling heappop and heappush separately. It raises an IndexError on an empty heap, so make sure there is at least one element before calling it.

import heapq

sensor_data = [5, 12, 18, 30, 25]
heapq.heapify(sensor_data)

print("Before heapreplace:", sensor_data)
replaced = heapq.heapreplace(sensor_data, 2)
print("Replaced value:", replaced)
print("After heapreplace:", sensor_data)
Before heapreplace: [5, 12, 18, 30, 25]
Replaced value: 5
After heapreplace: [2, 12, 18, 30, 25]

heapq.heappushpop(heap, item) is similar but smarter — it always returns whichever is smaller between the current heap minimum and the incoming item, without unnecessarily modifying the heap when the new item would have been popped right away.

import heapq

heap = [10, 20, 30]
heapq.heapify(heap)

result = heapq.heappushpop(heap, 5)
print("Returned:", result)
print("Heap after:", heap)
Returned: 5
Heap after: [10, 20, 30]

Since 5 was smaller than the current minimum 10, heappushpop returned it immediately and left the heap unchanged. This makes it the right choice for maintaining a fixed-size python min heap — for example, keeping a sliding window of the N largest values seen so far.

heapq merge — Combining Multiple Sorted Streams

heapq.merge() takes multiple already-sorted iterables and merges them into one sorted sequence lazily — meaning it yields results one at a time without loading everything into memory at once. This is ideal for merging log file chunks, paginated database results, or any streamed data.

import heapq

stream_a = [1, 4, 7, 10]
stream_b = [2, 5, 8, 11]
stream_c = [3, 6, 9, 12]

merged = heapq.merge(stream_a, stream_b, stream_c)
print(list(merged))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

Because heapq merge returns a generator, it works even when each individual stream is too large to hold in memory. One important caveat: all input iterables must already be sorted in ascending order. If they are not, the merged output will be incorrect. Python heapq does not validate this for you — it is your responsibility to ensure sorted inputs.

Building a Python Priority Queue with heapq

A python priority queue is a data structure where each element carries a priority, and items are dequeued in priority order rather than insertion order. Python heapq is the lightest way to build one without importing any additional libraries — no queue.PriorityQueue class needed.

The standard pattern is to store tuples where the first element is the numeric priority. Because python heapq is a min heap, lower numbers represent higher urgency.

import heapq

pq = []

heapq.heappush(pq, (3, "Send weekly report"))
heapq.heappush(pq, (1, "Fix production bug"))
heapq.heappush(pq, (2, "Review pull request"))
heapq.heappush(pq, (1, "Reply to urgent email"))

print("Processing tasks in priority order:")
while pq:
 priority, task = heapq.heappop(pq)
 print(f" Priority {priority}: {task}")
Processing tasks in priority order:
 Priority 1: Fix production bug
 Priority 1: Reply to urgent email
 Priority 2: Review pull request
 Priority 3: Send weekly report

When two tasks share the same priority, python heapq compares the second tuple element — in this case the string name alphabetically. If you are storing custom objects that are not comparable, add a monotonically increasing counter as the second element so tuples look like (priority, count, task). That gives the heap a clean numeric tiebreaker that never fails.

Simulating a Max Heap with python heapq

Since python heapq only supports a python min heap natively, the standard trick for getting max heap behavior is to negate all values before pushing and un-negate when popping.

import heapq

scores = [42, 85, 13, 67, 91, 38]
max_heap = []

for score in scores:
 heapq.heappush(max_heap, -score)

print("Scores in descending order:")
while max_heap:
 print(-heapq.heappop(max_heap))
Scores in descending order:
91
85
67
42
38
13

Negating the values inverts the heap property — the most negative stored value (representing the largest original number) always floats to the top. This negation pattern is widely used and works reliably for any numeric type in the heapq module python supports.

Full Working Example — Event-Driven Task Scheduler

This final example brings together python heapq, heappush, heappop, nsmallest, and the priority tuple pattern into a practical task scheduler class. It accepts tasks with priorities and due dates, processes them in order, and previews upcoming work without disturbing the heap.

import heapq

class TaskScheduler:
 def __init__(self):
 self._heap = []
 self._counter = 0

 def add_task(self, priority, due_date, description):
 entry = (priority, self._counter, due_date, description)
 heapq.heappush(self._heap, entry)
 self._counter += 1

 def next_task(self):
 if not self._heap:
 return None
 priority, _, due_date, description = heapq.heappop(self._heap)
 return priority, due_date, description

 def peek(self):
 if not self._heap:
 return None
 priority, _, due_date, description = self._heap[0]
 return priority, due_date, description

 def preview_top(self, n):
 top = heapq.nsmallest(n, self._heap)
 return [(p, d, desc) for p, _, d, desc in top]

 def __len__(self):
 return len(self._heap)


def main():
 scheduler = TaskScheduler()

 scheduler.add_task(2, "2026-05-10", "Migrate database schema")
 scheduler.add_task(1, "2026-04-27", "Deploy emergency hotfix")
 scheduler.add_task(3, "2026-06-01", "Write internal documentation")
 scheduler.add_task(1, "2026-04-28", "Notify affected customers")
 scheduler.add_task(2, "2026-05-15", "Conduct code review sprint")

 print(f"Tasks in queue: {len(scheduler)}")
 print(f"Next up: {scheduler.peek()}
")

 print("Top 3 upcoming tasks (preview only):")
 for priority, due_date, description in scheduler.preview_top(3):
 print(f" [{priority}] {description} — due {due_date}")

 print("
Processing all tasks:
")
 while len(scheduler) > 0:
 priority, due_date, description = scheduler.next_task()
 print(f" Priority {priority} | Due: {due_date} | Task: {description}")


if __name__ == "__main__":
 main()

Output

Tasks in queue: 5
Next up: (1, '2026-04-27', 'Deploy emergency hotfix')

Top 3 upcoming tasks (preview only):
 [1] Deploy emergency hotfix — due 2026-04-27
 [1] Notify affected customers — due 2026-04-28
 [2] Migrate database schema — due 2026-05-10

Processing all tasks:

 Priority 1 | Due: 2026-04-27 | Task: Deploy emergency hotfix
 Priority 1 | Due: 2026-04-28 | Task: Notify affected customers
 Priority 2 | Due: 2026-05-10 | Task: Migrate database schema
 Priority 2 | Due: 2026-05-15 | Task: Conduct code review sprint
 Priority 3 | Due: 2026-06-01 | Task: Write internal documentation

The _counter field acts as a stable tiebreaker so equal-priority tasks never trigger a comparison between due date strings in an unexpected order. The peek() method reads index zero directly for O(1) access — a key advantage of the python heapq model — and preview_top() calls heapq nsmallest to return a sorted preview without ever modifying the live heap underneath.