Skip to Content
Course content

452: Solving Common Heap and Priority Queue Problems

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a PR for a colleague who was building a real-time notification system. He needed to pull the "top 10 most urgent" alerts from a stream of thousands of events every few seconds. His solution was simple: collect all the alerts in a list, call .sort() on the whole thing, and slice the first ten. It worked fine in staging with a hundred events. But the moment we hit production and the event volume spiked to 50,000 per window, the CPU on the worker nodes pegged at 100%. He was spending an enormous amount of compute power sorting 49,990 items that he was just going to throw away.

This is the classic trap of using a full sort when you actually need a heap. When you only care about the extremums—the smallest, the largest, or the "top K"—sorting the entire dataset is a waste of resources. That's where the heapq module becomes your best friend.

Why Sorting Everything is a Trap

In Python, list.sort() is incredibly optimized, but it's still $O(N \log N)$. If you only need the top 10 items, you can maintain a heap of just those 10 items. As you iterate through your data, you compare the current item to the "worst" item in your top 10. If the new item is better, you pop the old one and push the new one. This drops your complexity to $O(N \log K)$, where $K$ is the number of elements you're tracking. When $K$ is 10 and $N$ is 50,000, the performance difference is staggering.

Python's heapq provides nlargest and nsmallest functions that handle this logic for you under the hood. I usually suggest these for readability unless you're implementing a custom priority queue where items are being added and removed dynamically over a long period.

import heapq

# Imagine these are our urgent alert scores
alerts = [12, 45, 2, 89, 34, 100, 23, 67, 11, 90]

# Instead of sorted(alerts, reverse=True)[:3]
top_three = heapq.nlargest(3, alerts) 
print(top_three)  # [100, 90, 89]

Wrestling with Python's Min-Heap

Here is the part that trips everyone up: Python’s heapq is a min-heap. This means heapq.heappop() always gives you the smallest element. If you're trying to build a priority queue where the "highest" number represents the highest priority, you'll find that Python is giving you the opposite of what you want. I've seen countless developers spend an hour debugging this only to realize the heap is working exactly as designed.

The industry-standard workaround is the "negative trick." By multiplying your priority values by -1 before pushing them onto the heap, you effectively turn the min-heap into a max-heap. When you pop the value, you just multiply it by -1 again to restore the original number. It feels a bit hacky, but it's the most efficient way to handle max-priority logic in Python.

When your priority is more complex than a single number—say, a priority level and a timestamp—you can push tuples into the heap. Python compares tuples element by element. If the first elements (the priority) are tied, it moves to the second element (the timestamp) to break the tie.

import heapq

# Priority Queue for tasks: (priority, timestamp, task_name)
# Note: Lower priority number = higher importance in a min-heap
pq = []
heapq.heappush(pq, (2, 1625097600, "Low priority task"))
heapq.heappush(pq, (1, 1625097601, "High priority task"))
heapq.heappush(pq, (1, 1625097500, "Urgent task (older)"))

# This will pop the highest priority (1), 
# then the one with the smallest timestamp (the oldest)
while pq:
    priority, time, task = heapq.heappop(pq)
    print(f"Processing {task} with priority {priority}")

One last tip: if you find yourself needing to update the priority of an item already in the heap, heapq doesn't support that directly. The cleanest way to handle this is to mark the old entry as "removed" (using a dictionary to track valid entries) and simply push a new entry with the updated priority. This is much faster than trying to find and re-sort the internal heap array.




📋 Practical Task

Exercise: Build a Critical Incident Alert Dispatcher

You are building a system that manages emergency server alerts. Alerts arrive with a severity level (1 for Critical, 2 for Warning, 3 for Info) and a timestamp. The system must always process the most severe alert first. If two alerts have the same severity, the one that occurred first (the smallest timestamp) must be processed first.

Your Task: Implement a class IncidentDispatcher with the following requirements:

  • A method add_incident(severity, timestamp, message) that adds an incident to the queue.
  • A method process_next() that removes and returns the message of the highest-priority incident. If the queue is empty, return None.

Test Case to Validate:

dispatcher = IncidentDispatcher()
dispatcher.add_incident(2, 1000, "Disk space warning")
dispatcher.add_incident(1, 1005, "CPU Overheat")
dispatcher.add_incident(1, 1002, "Database Connection Lost")
dispatcher.add_incident(3, 999, "Log rotation complete")

# Should process "Database Connection Lost" first (Severity 1, Timestamp 1002)
# Then "CPU Overheat" (Severity 1, Timestamp 1005)
# Then "Disk space warning" (Severity 2)
# Then "Log rotation complete" (Severity 3)
Rating
0 0

There are no comments for now.

to be the first to leave a comment.