skip to content

questions

4

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

open as a page

Why can heapq.heappush raise TypeError when two entry tuples tie on priority?

level: middleimportance: must knowfreq 55%

basics

~20 s

Tuples compare element by element. When two priorities are equal, Python moves on to the next field and compares the payloads themselves; if those are dicts or plain objects with no ordering, the comparison raises TypeError.

open as a page

heapq.merge output in a nightly pipeline arrives out of order and downstream silently truncates - what went wrong?

level: seniorimportance: should knowfreq 28%

basics

~20 s

heapq.merge does not sort. It assumes every input iterable is already ordered, and it never checks. One unsorted source makes the merged stream go backwards, and any consumer that stops at the first out-of-order record truncates without error.

open as a page

How do heapq.heappushpop and heapq.heapreplace differ on the same list?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

The order of the two operations differs. heappushpop pushes first, so it can hand straight back the item you passed. heapreplace pops first, so it always evicts the current smallest, even if the new item is smaller.

open as a page