How to use 'heapq' in Python

Learn to use Python's heapq module. This guide covers methods, tips, real-world applications, and debugging common errors.

How to use 'heapq' in Python
Published on: 
Tue
Mar 10, 2026
Updated on: 
Fri
Mar 13, 2026
The Replit Team

Python's heapq module provides an efficient way to implement min-heaps. It's perfect for tasks that require quick access to the smallest item without sorting the entire collection.

In this article, you'll explore essential techniques and tips for using heapq. You'll find real-world applications and debugging advice to help you master this powerful tool for your projects.

Basic usage of the heapq module

import heapq

numbers = [3, 1, 4, 1, 5, 9, 2]
heapq.heapify(numbers)
print(numbers)--OUTPUT--[1, 1, 2, 3, 5, 9, 4]

The heapq.heapify() function transforms the numbers list into a min-heap in-place. This is a linear-time operation, making it more efficient than building a heap by pushing elements individually. The resulting list isn't fully sorted, which is a common point of confusion.

  • The heap property guarantees only that the smallest element is at the root, which is why 1 is at numbers[0].
  • Every parent node is smaller than or equal to its children, but the rest of the list doesn't follow a simple sorted order.

Core functionality

While heapify offers a quick way to create a heap, the core functions are what you'll use to dynamically add, remove, and inspect elements.

Using heappush and heappop operations

import heapq

heap = []
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 4)
print(heap)

smallest = heapq.heappop(heap)
print(f"Popped: {smallest}, Remaining: {heap}")--OUTPUT--[1, 3, 4]
Popped: 1, Remaining: [3, 4]

The heappush() function adds an element while preserving the heap structure. When you push items, they aren't just appended; the heap is reordered to keep the smallest element at the root. This is why pushing 1 after 3 correctly places 1 at the front.

  • heappop() removes and returns this smallest item.
  • After an element is popped, the heap reorganizes to ensure the next smallest value moves to the root, maintaining the heap property for future operations.

Converting a list to a heap with heapify

import heapq

data = [5, 7, 9, 1, 3]
print(f"Before heapify: {data}")

heapq.heapify(data)
print(f"After heapify: {data}")--OUTPUT--Before heapify: [5, 7, 9, 1, 3]
After heapify: [1, 3, 9, 5, 7]

The heapify() function is the most efficient way to turn an existing list into a heap. It’s an in-place operation, meaning it directly rearranges the elements within your original list, data, instead of creating a new one. This makes it ideal for initializing a heap from a pre-existing collection of items.

  • The function ensures the smallest item, 1, is moved to the root at index 0.
  • Notice the rest of the list isn't sorted. The elements are simply reordered to satisfy the heap property.

Accessing heap elements without popping

import heapq

heap = [1, 5, 3, 7, 9, 2]
heapq.heapify(heap)
print(f"Heap: {heap}")

smallest = heap[0]
print(f"Smallest item: {smallest}")--OUTPUT--Heap: [1, 5, 2, 7, 9, 3]
Smallest item: 1

You don't always need to remove the smallest item to know what it is. Because a min-heap guarantees the smallest element is at the root, you can peek at it with a simple index lookup.

  • Access the item directly using heap[0].
  • This action is read-only; it doesn't modify the heap's structure at all.
  • It’s an efficient way to check the next-up item before deciding whether to heappop() it.

Advanced applications

Beyond the basics, heapq unlocks powerful patterns for implementing max heaps, efficiently finding the largest or smallest items, and building sophisticated priority queues.

Implementing a max heap with heapq

import heapq

numbers = [3, 1, 4, 1, 5, 9, 2]
max_heap = [-num for num in numbers]
heapq.heapify(max_heap)

largest = -heapq.heappop(max_heap)
print(f"Largest element: {largest}")--OUTPUT--Largest element: 9

Since heapq is a min-heap implementation, you can't directly find the largest element. The standard workaround is to invert the sign of each number before adding it to the heap. This clever trick makes the largest original values the smallest in the heap.

  • The list comprehension [-num for num in numbers] creates a new list where every number is negated.
  • After running heapify(), the smallest item, which is the most negative number, is at the root.
  • When you heappop() this value, you just negate it again to retrieve the original largest number.

Using nlargest and nsmallest functions

import heapq

numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
k = 3

k_largest = heapq.nlargest(k, numbers)
k_smallest = heapq.nsmallest(k, numbers)
print(f"{k} largest: {k_largest}")
print(f"{k} smallest: {k_smallest}")--OUTPUT--3 largest: [9, 6, 5]
3 smallest: [1, 1, 2]

For finding just the top few items in a collection, heapq provides two highly optimized functions. Using nlargest() and nsmallest() is much more efficient than sorting the entire list, especially when you only need a small subset.

  • The nlargest(k, numbers) function returns a list of the k largest items from the numbers iterable.
  • Similarly, nsmallest(k, numbers) gives you the k smallest items.

These functions are ideal for tasks like finding the top three scores or the five cheapest products without the overhead of a full sort.

Creating a priority queue with custom objects

import heapq

class Task:
def __init__(self, priority, description):
self.priority = priority
self.description = description

def __lt__(self, other):
return self.priority < other.priority

task_queue = []
heapq.heappush(task_queue, Task(3, "Low priority"))
heapq.heappush(task_queue, Task(1, "High priority"))
heapq.heappush(task_queue, Task(2, "Medium priority"))

next_task = heapq.heappop(task_queue)
print(f"Next task: {next_task.description} (priority: {next_task.priority})")--OUTPUT--Next task: High priority (priority: 1)

You can use heapq with custom objects, not just numbers. The trick is to tell the heap how to compare your objects. For the Task class, this is achieved by implementing the __lt__ (less-than) dunder method, which defines the sorting logic.

  • The method def __lt__(self, other): return self.priority < other.priority tells the heap that a task with a lower priority number is "smaller."
  • This makes heappop() always return the item with the lowest priority value, effectively creating a priority queue.

Move faster with Replit

Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.

For the heapq techniques covered in this article, Replit Agent can turn them into production-ready tools. You could:

  • Build a smart task scheduler that automatically surfaces the most urgent items first, just like a priority queue.
  • Create a real-time leaderboard that efficiently tracks top scores in a game using nlargest.
  • Deploy a price monitoring utility that identifies the most expensive products from a live data feed by simulating a max heap.

Describe your app idea, and Replit Agent writes the code, tests it, and fixes issues automatically, all in your browser.

Common errors and challenges

While heapq is powerful, a few common mistakes can trip you up if you're not careful when managing your heap data structure.

Avoiding direct modification of heap elements

Directly changing an element's value in a heap, like with my_heap[i] = new_value, breaks the heap property because it doesn't re-sort the structure. The heap becomes invalid, and future operations won't work as expected. The only safe way to update an item is to remove it, change its value, and push it back, but this is inefficient. A better approach is often to add the new, updated item and simply ignore the old one when it's eventually popped.

Implementing proper comparison for custom objects in heapq

When using custom objects, you must tell Python how to compare them. If you push objects to a heap without a comparison method like __lt__, Python will raise a TypeError because it doesn't know which object is "smaller." For more complex sorting, you can use tuples to create tie-breaking rules. For instance, comparing (priority, timestamp) ensures that if two tasks have the same priority, the older one is processed first.

Understanding the difference between heappushpop and heapreplace

The functions heappushpop() and heapreplace() both maintain a fixed-size heap but behave differently. heappushpop(heap, item) pushes the new item first and then pops the smallest, so the returned item could be the one you just added. In contrast, heapreplace(heap, item) pops the smallest item first and then pushes the new one. This is slightly more efficient and guarantees the returned item was already in the heap. Choose heapreplace() when you're sure the heap isn't empty and you're simply swapping out the smallest element.

Avoiding direct modification of heap elements

It's tempting to modify a heap element directly, since it's just a list. However, an assignment like heap[i] = new_value bypasses the heap algorithm and corrupts the structure. The heap property is broken, leading to incorrect results. See what happens below.

import heapq

heap = [1, 5, 2, 7]
heapq.heapify(heap)
print(f"Original heap: {heap}")

# Direct modification breaks heap property
heap[1] = 0
print(f"After modification: {heap}")

Assigning heap[1] = 0 places a smaller value (0) as a child of a larger one (1), violating the heap property. The smallest element is no longer at the root. The following example shows the correct way to handle updates.

import heapq

heap = [1, 5, 2, 7]
heapq.heapify(heap)
print(f"Original heap: {heap}")

# Correct approach: Remove and add elements properly
value_to_remove = heap[1]
heap[1] = heap[-1]
heap.pop()
heapq.heapify(heap)
heapq.heappush(heap, 0)
print(f"After proper modification: {heap}")

Instead of direct assignment, you must rebuild the heap to update an element. The correct but inefficient approach involves a few steps:

  • Replace the target element with the last one in the list.
  • Pop the last element and call heapify() to fix the structure.
  • Finally, use heappush() to add the new value.

This multi-step process is necessary to preserve the heap property, ensuring future operations work correctly.

Implementing proper comparison for custom objects in heapq

Using custom objects in a heap is powerful, but it comes with a common pitfall. Python can't magically guess how to order your objects—you must define the comparison logic. Without it, heapq raises a TypeError, as the following example demonstrates.

import heapq

class Task:
def __init__(self, priority, name):
self.priority = priority
self.name = name

tasks = [
Task(3, "Low priority"),
Task(1, "High priority"),
Task(2, "Medium priority")
]

heapq.heapify(tasks) # This will raise TypeError

This code fails because heapify can't compare two Task objects. Without a defined comparison method, Python doesn't know which task is "smaller" than another. The next example demonstrates the correct implementation.

import heapq

class Task:
def __init__(self, priority, name):
self.priority = priority
self.name = name

def __lt__(self, other):
return self.priority < other.priority

tasks = [
Task(3, "Low priority"),
Task(1, "High priority"),
Task(2, "Medium priority")
]

heapq.heapify(tasks)
print(f"Top priority task: {tasks[0].name}")

The fix is to tell Python how to compare your custom objects by implementing the __lt__ (less-than) method in your class. This method defines the sorting logic. For instance, return self.priority < other.priority instructs heapq to consider the object with the lower priority value as "smaller."

  • This allows functions like heapify() to correctly build the heap.
  • The object with the lowest priority number will always end up at the root.

Understanding the difference between heappushpop and heapreplace

The functions heappushpop() and heapreplace() seem similar, but their order of operations creates a crucial difference. One pushes first, then pops, while the other pops then pushes. It's a subtle distinction that affects which element gets returned. See what happens below.

import heapq

heap = [1, 5, 3]
heapq.heapify(heap)

# Using heapreplace when heappushpop is needed
smallest = heapq.heapreplace(heap, 4)
print(f"Smallest: {smallest}, Heap: {heap}") # Removes 1 first!

The function heapreplace() removes 1 without ever comparing it to the new item, 4. This happens because it always pops an existing item, which isn't what you want for this task. See how the correct function handles this below.

import heapq

heap = [1, 5, 3]
heapq.heapify(heap)

# heappushpop pushes first, then pops
smallest = heapq.heappushpop(heap, 4)
print(f"Smallest: {smallest}, Heap: {heap}") # 1 is still smallest

# heapreplace pops first, then pushes
heap2 = [1, 5, 3]
heapq.heapify(heap2)
smallest = heapq.heapreplace(heap2, 0)
print(f"Smallest: {smallest}, Heap: {heap2}")

The difference between heappushpop() and heapreplace() is all about timing, which is crucial when managing a fixed-size heap.

  • heappushpop() pushes the new item first, then pops the smallest overall. The returned item could be the one you just added if it's the smallest.
  • heapreplace() pops the smallest item first, then pushes the new one. It's more efficient and guarantees the returned item was already in the heap. Use it only on non-empty heaps.

Real-world applications

With a firm grasp on how heapq works and what pitfalls to avoid, you can build efficient, real-world tools.

Tracking largest elements in a data stream with heapq

A min-heap provides an efficient way to track the top k largest elements in a data stream without needing to store the entire collection.

The strategy is to maintain a min-heap that holds up to k items. As you process the stream, you compare each new item to the smallest value in your heap, which is always at the root.

  • If a new item is larger than the heap's smallest element, you know it belongs among the top candidates.
  • You can then use heapq.heappushpop() to efficiently replace that smallest element with the new, larger one, maintaining the heap's size.
  • This process ensures the heap always contains the k largest items encountered so far, making it a memory-friendly approach for large datasets.

import heapq

def track_top_k(stream, k):
top_items = []
for item in stream:
if len(top_items) < k:
heapq.heappush(top_items, item)
elif item > top_items[0]:
heapq.heappushpop(top_items, item)
return sorted(top_items, reverse=True)

data = [4, 7, 1, 9, 3, 6, 8, 2, 5]
print(f"Top 3 elements: {track_top_k(data, 3)}")

This function, track_top_k, cleverly finds the largest numbers in a collection without sorting the whole thing. It uses a min-heap to hold only the top k candidates at any time.

  • The loop first populates the heap with the initial k items from the stream.
  • After that, it only adds a new item if it's larger than the smallest one already in the heap—the check is item > top_items[0].
  • The heappushpop() function efficiently swaps the new, larger item for the old smallest one, maintaining the heap's size.

Finally, it sorts the resulting heap to present the top items in descending order.

Implementing a simple job scheduler with heapq

A min-heap is a natural fit for building a job scheduler, as it allows you to process tasks in order of importance by always handling the highest-priority item first.

import heapq

# (priority, job_name) - lower number = higher priority
jobs = [(3, "Generate report"), (1, "Process payments"), (2, "Send notifications")]
heapq.heapify(jobs)

while jobs:
priority, job = heapq.heappop(jobs)
print(f"Executing: {job} (priority: {priority})")

This code leverages Python's tuple comparison to build a simple priority queue. When you call heapq.heapify() on the jobs list, it arranges the tuples based on their first element—the priority number. This automatically places the tuple with the lowest priority value at the root of the heap.

The while loop then systematically processes the heap.

  • Each call to heappop() removes and returns the tuple with the smallest priority number.
  • The tuple is unpacked into priority and job variables before its details are printed.

Get started with Replit

Turn these concepts into a real tool. Describe your idea to Replit Agent, like “build a live stock tracker for the top 5 performing assets” or “create a simple task manager that prioritizes jobs.”

The agent writes the code, tests for errors, and handles deployment for you. Start building with Replit and bring your ideas to life.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

Get started for free

Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.