skip to content

Python's heapq is a min-heap - how do you get max-heap behaviour from it?

level: juniorimportance: must knowfreq 70%

answer

  1. Functions over a list, not a class
  2. Only one end is guaranteed
  3. Sign arithmetic on the ordering key
  4. 3.14 added mirrored function names
  5. One-shot ranking has its own helper

basics

~20 s

heapq keeps the smallest item at index 0 and has no reverse switch. Negate the ordering key on the way in and back on the way out, or on Python 3.14 call its max-heap functions such as heapify_max and heappop_max.

solid answer

~40 s

`heapq` is not a class — it is a set of functions that maintain the min-heap invariant over an ordinary `list`, so `heap[0]` is always the smallest element and `heapq.heappop` always returns that one. There is no `reverse=` parameter anywhere in the module. Three routes give you largest-first access. The portable one is sign negation: push `-value`, or `(-priority, item)`, and negate again after popping — arithmetic only, so it works for numbers and fails for `str` or `datetime`. For a one-shot answer, `heapq.nlargest` reads the whole iterable and hands back the biggest items directly. Since **Python 3.14** the module also exposes real max-heap functions — `heapify_max`, `heappush_max`, `heappop_max`, `heapreplace_max`, `heappushpop_max` — which maintain the mirrored invariant over the same plain list. Never mix the two families on one list.

code

python · 12 lines
python
import heapq

# Portable: push the negated key, negate again on the way out
h = []
for score in (5, 1, 9):
    heapq.heappush(h, -score)
print(-heapq.heappop(h))

# Python 3.14: real max-heap helpers over the same plain list
m = [5, 1, 9]
heapq.heapify_max(m)
print(m[0], heapq.heappop_max(m))

go deeper

for a junior

Be ready to say that heapq is a set of functions over an ordinary list, that heap[0] is the smallest element, and that reversing the order means negating the key or, on 3.14, calling the max-heap functions.

for a middle

Explain that heapify mutates in place and returns None, that the invariant covers index 0 only, and why negation breaks for non-numeric keys. Know when nlargest replaces a heap entirely.

for a senior

Show the judgement of picking a live heap over a one-shot ranking, and flag the real hazard: nothing validates the invariant, so a list mutated behind heapq's back yields wrong answers silently rather than raising.

for a principal

Own the portability call. Committing to the 3.14 max-heap functions sets an interpreter floor for the whole codebase; negation keeps you portable at the cost of an encoding every reader must decode. Decide once and write it down.

### There is no heap object The first thing to say out loud in an interview is that `heapq` gives you *functions*, not a type. `heapq.heapify(data)` rearranges the existing `list` in place and returns `None`; `heapq.heappush(data, item)` appends and restores the invariant; `heapq.heappop(data)` removes and returns `data[0]`. The container stays a plain `list` the whole time, which is why you can `len()` it, slice it, pickle it or pass it to a function that has never heard of `heapq`. It also means nothing stops you from mutating that list behind the module's back — `data.append(x)` or `data.sort(reverse=True)` leaves an object that still looks like a list but no longer satisfies what the next `heappop` assumes, and you get silently wrong results rather than an exception. The invariant the module maintains is one-sided: **the smallest element is at index 0**. Nothing else is ordered. A common beginner claim is that `heapify` sorts the list, or that `data[-1]` is the maximum. Neither is true; only position 0 carries a guarantee. ### Why min-only, and the three ways around it The module was written around a single ordering direction and, until recently, exposed only that. So for largest-first access you pick one of three tools. **1. Negation.** Push the arithmetic negative of the ordering key and undo it on the way out: ```python import heapq largest_first = [] for score in (5, 1, 9): heapq.heappush(largest_first, -score) top = -heapq.heappop(largest_first) # 9 ``` With payloads you negate only the priority field of the entry tuple: `heapq.heappush(h, (-priority, item))`. The limitation is that this is real arithmetic. `-"gene"` and `-datetime.date.today()` both raise `TypeError`, so for non-numeric orderings you either derive a numeric surrogate (a negated POSIX timestamp, a negated code point sequence) or wrap the value in a small class whose `__lt__` is inverted. **2. `heapq.nlargest` / `heapq.nsmallest`.** When you want an answer rather than a live structure, `heapq.nlargest(3, data)` and its `key=` parameter give you the result directly and you never touch a heap at all. This is the right call for a one-pass question over a finite iterable; it is the wrong call when items keep arriving and you must pop as you go. **3. The 3.14 max-heap functions.** Python 3.14 promoted the module's long-standing private max-heap helpers to public names: `heapify_max`, `heappush_max`, `heappop_max`, `heapreplace_max` and `heappushpop_max`. They maintain the mirror invariant — largest at index 0 — over the same ordinary list: ```python import heapq readings = [5, 1, 9] heapq.heapify_max(readings) readings[0] # 9 heapq.heappop_max(readings) # 9 ``` On 3.13 and earlier those names do not exist; code that must run on older interpreters uses negation. And the two families are not interchangeable on one list: a list built with `heappush` satisfies the min invariant, and calling `heappop_max` on it returns garbage without complaining. ### What interviewers are really checking Three things. First, that you know the container is a list and the module is a set of functions over it — candidates who reach for a nonexistent `heapq.Heap()` or pass `reverse=True` have never used it. Second, that you can state the invariant precisely: index 0 only. Third, that you pick the right one of the three tools for the shape of the problem — a live priority structure wants a heap, a one-shot ranking wants `nlargest`, and a portable max-heap on an older interpreter wants negation. A useful closing detail: `heapq.heapify` works on any list you already have, so the usual pattern is to build the list however you like and heapify once, rather than pushing elements one at a time. And if you find yourself needing both directions over the same data at once, that is a signal you want two structures, not one clever encoding.

  • Why does the negation trick fail for a heap keyed on strings or dates?
    Because negation is arithmetic, not ordering. `str` and `datetime` objects have no `__neg__`, so `-value` raises `TypeError`. Either derive a numeric surrogate you can negate — a negated timestamp, for instance — or wrap each entry in a small class whose `__lt__` compares the other way round, or use the 3.14 max-heap functions.
  • What happens if you call heapq.heappop on a list you never heapified?
    Nothing raises. The functions trust the invariant rather than verifying it, so on an arbitrary list `heappop` returns element 0 — whatever happened to be first — and then rearranges the rest under an assumption that never held. You get plausible, wrong answers. Always `heapq.heapify` a pre-existing list before treating it as a heap.
  • Can you keep both a min-heap and a max-heap view over the same list object?
    No. The two function families maintain incompatible invariants at index 0, so a list can satisfy only one at a time. If you need both directions, keep two lists holding the same items — which is what a two-structure design does — and accept the duplicated storage and the synchronisation cost.

The module is a set of tools for keeping one list tidy at one end, like a spike file that always shows the earliest docket on top - to see the latest first you either file everything with the dates flipped or use a second spike that stacks the other way.

saying these in an interview costs you the question

  • Claims heapq exports a Heap or MaxHeap class
  • Passes reverse=True to heapify or heappush
  • Says the list is fully sorted after heapify
  • Assumes heap[-1] holds the largest element
  • Expects heapify to return a new heap object
  • Tries to negate a string to reverse the order

context