heapq
custom predicate
Python
priority queue
sorting

heapq with custom compare predicate

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Python's heapq module does not accept a custom comparator or predicate argument. If you need custom ordering, the standard approach is to push values that already compare the way you want, usually tuples or small wrapper objects with a defined ordering.

Why There Is No Comparator Parameter

heapq works directly on Python objects and relies on their normal less-than comparison behavior. Unlike some other languages' priority queue APIs, it does not let you pass a comparison function such as cmp(a, b) or a predicate.

That means you solve custom priority by changing what goes into the heap, not by configuring the heap itself.

The Most Common Pattern: Store a Sort Key

If the ordering can be represented by a simple key, push a tuple where the first element is the priority.

python
1import heapq
2
3heap = []
4
5items = [
6    (3, "low"),
7    (1, "high"),
8    (2, "medium"),
9]
10
11for priority, value in items:
12    heapq.heappush(heap, (priority, value))
13
14while heap:
15    print(heapq.heappop(heap))

Output comes out in ascending priority order because tuples compare element by element.

This is usually the best answer because it is simple, fast, and explicit.

Use a Tie-Breaker for Stability

If two items may share the same priority and the payload objects are not directly comparable, add a monotonic counter.

python
1import heapq
2import itertools
3
4counter = itertools.count()
5heap = []
6
7heapq.heappush(heap, (2, next(counter), {"task": "A"}))
8heapq.heappush(heap, (2, next(counter), {"task": "B"}))
9
10print(heapq.heappop(heap))
11print(heapq.heappop(heap))

The counter ensures a stable and always-comparable second field, which avoids errors when Python tries to compare two dictionaries or other non-orderable objects.

Wrapper Objects with __lt__

If the ordering logic is more complex, wrap the data in a class and define __lt__.

python
1import heapq
2
3
4class PriorityItem:
5    def __init__(self, importance, timestamp, payload):
6        self.importance = importance
7        self.timestamp = timestamp
8        self.payload = payload
9
10    def __lt__(self, other):
11        if self.importance != other.importance:
12            return self.importance > other.importance
13        return self.timestamp < other.timestamp
14
15
16heap = []
17heapq.heappush(heap, PriorityItem(1, 10, "a"))
18heapq.heappush(heap, PriorityItem(2, 5, "b"))
19heapq.heappush(heap, PriorityItem(1, 8, "c"))
20
21while heap:
22    item = heapq.heappop(heap)
23    print(item.payload)

This example makes higher importance values come out first, then uses earlier timestamp values as the tie-breaker.

A dataclass Version Can Be Cleaner

For simple wrappers, dataclasses can reduce boilerplate.

python
1from dataclasses import dataclass, field
2import heapq
3
4
5@dataclass(order=True)
6class Task:
7    priority: int
8    payload: str = field(compare=False)
9
10
11heap = []
12heapq.heappush(heap, Task(1, "critical"))
13heapq.heappush(heap, Task(3, "low"))
14heapq.heappush(heap, Task(2, "medium"))
15
16while heap:
17    print(heapq.heappop(heap))

Fields with compare=False stay attached to the object without affecting heap order.

Max-Heap Behavior Is Usually Simulated

Because heapq is a min-heap, many "custom comparator" questions are really just max-heap questions. The simplest solution is to negate the numeric priority.

python
1import heapq
2
3heap = []
4heapq.heappush(heap, (-10, "highest"))
5heapq.heappush(heap, (-5, "middle"))
6heapq.heappush(heap, (-1, "lowest"))
7
8while heap:
9    print(heapq.heappop(heap))

Negating the priority turns the smallest stored value into the largest logical priority.

Common Pitfalls

The most common mistake is looking for a comparator parameter that heapq simply does not have. Another is pushing tuples whose later elements are not comparable when priorities tie, which causes runtime errors during heap operations. Developers also implement complex __lt__ logic without making it consistent, which can lead to confusing ordering behavior. In many cases, a plain tuple key is simpler and safer than a custom class.

Summary

  • 'heapq has no custom comparator argument.'
  • Use tuples or wrapper objects whose natural ordering matches the desired priority.
  • Add a tie-breaker counter when equal priorities are possible.
  • Use __lt__ or ordered dataclasses for more complex ordering rules.
  • For max-heap behavior, negate the priority or otherwise invert the key.

Course illustration
Course illustration

All Rights Reserved.