Java PriorityQueue
Java's PriorityQueue: natural and custom ordering, the min-heap default, max-heaps through comparators and the missing efficient arbitrary removal.
Learning goals
- Choose between natural ordering and a custom comparator
- Remember that the default is a min-heap and that iteration is not sorted
- Build a max-heap safely and avoid the subtraction-comparator bug
- Work around the O(n) cost of removing an arbitrary element
Prerequisites
PriorityQueue is the heap you will actually use. Knowing four of its behaviours prevents most of the mistakes people make with it.
Natural vs custom comparator
With no comparator, a PriorityQueue orders its elements by their natural order (Comparable), so elements must implement Comparable or construction fails at the first insertion. With a Comparator, the comparator decides, and it also decides which elements count as equal. Build comparators with Comparator.comparingInt(...), reversed() and thenComparing(...).
The queue below holds strings: by natural order apple is first; by length fig is first. Equal-priority elements come out in **no guaranteed order**, since a heap is not stable; add a sequence number to the comparator if arrival order must break ties.
1static void orderings() {2PriorityQueue<String> natural = new PriorityQueue<>(List.of("pear", "fig", "apple"));3System.out.println(natural.poll()); // apple: alphabetical order4PriorityQueue<String> byLength = new PriorityQueue<>(Comparator.comparingInt(String::length));5byLength.addAll(List.of("pear", "fig", "apple"));6System.out.println(byLength.poll()); // fig: the shortest string7}
Min-heap default
A PriorityQueue is a **min-heap**: peek and poll return the smallest element. Its toString and iterator expose the underlying **heap array**, not sorted order: after adding 5, 3, 8 and 1 it prints [1, 3, 8, 5], while polling gives 1, 3, 5, 8. To get sorted output, poll repeatedly (O(n log n) in all).
Costs from the documentation: offer, add, poll, remove() and peek and element are O(log n), O(log n), O(log n), O(log n) and O(1); contains and remove(Object) are O(n). Passing a collection to the constructor builds the heap in O(n).
1static void minHeapDefault() {2PriorityQueue<Integer> pq = new PriorityQueue<>();3for (int x : new int[]{5, 3, 8, 1}) pq.add(x);4System.out.println(pq); // [1, 3, 8, 5]: the heap array, not sorted5System.out.println(pq.peek()); // 16StringBuilder sb = new StringBuilder();7while (!pq.isEmpty()) sb.append(pq.poll()).append(' ');8System.out.println(sb.toString().trim()); // 1 3 5 89}
Max-heap comparator
For a **max-heap**, pass Collections.reverseOrder() or Comparator.reverseOrder() (natural order reversed), or Comparator.comparingInt(f).reversed() for a field. Do not write (a, b) -> b - a: the subtraction **overflows** when the values are far apart, and the heap silently misorders them.
With the values 1 and Integer.MIN_VALUE the subtraction version considers 1 smaller, so it polls Integer.MIN_VALUE first, which is wrong for a max-heap. Use Integer.compare(b, a) if a lambda is needed.
1static void maxHeap() {2PriorityQueue<Integer> good = new PriorityQueue<>(Collections.reverseOrder());3good.addAll(List.of(1, Integer.MIN_VALUE, 7));4System.out.println(good.poll()); // 7: the largest first56PriorityQueue<Integer> broken = new PriorityQueue<>((a, b) -> b - a); // overflows near the limits7broken.addAll(List.of(1, Integer.MIN_VALUE));8System.out.println(broken.poll()); // -2147483648: wrong, the smaller value came out first910PriorityQueue<Integer> fixed = new PriorityQueue<>((a, b) -> Integer.compare(b, a));11fixed.addAll(List.of(1, Integer.MIN_VALUE));12System.out.println(fixed.poll()); // 113}
Lack of efficient arbitrary removal
PriorityQueue.remove(Object) and contains(Object) do a **linear scan** with equals, not the comparator, so they cost O(n) and can disagree with the ordering: with a comparator that ranks by length, the queue holding "aa" does not contain "bb" although the comparator treats them as equal. There is also no decreaseKey and no way to find an element's position.
Workarounds: use **lazy deletion** (mark removed items and skip them when polled, as in Dijkstra and the sliding median), re-insert an updated copy and skip the stale one, use a TreeSet (O(log n) removal and neighbours, with a unique tiebreaker in the comparator), or write an **indexed priority queue**.
1static void arbitraryRemoval() {2PriorityQueue<String> pq = new PriorityQueue<>(Comparator.comparingInt(String::length));3pq.add("aa"); pq.add("c"); pq.add("bbb");4System.out.println(pq.contains("zz")); // false: contains() uses equals, not the comparator5System.out.println(pq.remove("zz")); // false: nothing removed6System.out.println(pq.remove("c")); // true, found by a linear scan7System.out.println(pq.poll()); // aa8}
Practice ladder
Now try it yourself
Easy
- Last Stone Weight
- Relative Ranks
Medium
- Top K Frequent Words
- Reorganize String
- Task Scheduler
Advanced
- Sliding Window Median
- Minimum Cost to Hire K Workers
Key takeaways
- PriorityQueue orders by Comparable or a Comparator; ties come out in no guaranteed order.
- It is a min-heap: toString and the iterator show the heap array, not sorted order.
- Use Collections.reverseOrder() or Integer.compare for a max-heap; never b - a.
- remove(Object) and contains are O(n) and use equals; use lazy deletion, a TreeSet or an indexed queue instead.