Hook: Interviewers love this question because PriorityQueue looks like a simple queue, but the hidden heap rules explain almost every behavior and gotcha.
Question: PriorityQueue internal implementation.
Answer: In Java, PriorityQueue is backed by a binary heap, which is a complete tree stored inside an array. By default it is a min-heap, so the smallest element is always at the head, but the rest of the elements are only partially ordered. That is why peek() is fast, while offer() and poll() do a little reordering work.
Interview-Ready Answer: I’d say: Java’s PriorityQueue is implemented as a binary heap stored in an array. The head is the smallest element by natural order or by the provided Comparator, so peek() is O(1) and offer()/poll() are O(log n). The important gotcha is that iteration is not sorted, nulls are forbidden, and the queue is not thread-safe.
A PriorityQueue is a queue where the next element removed is the one with the highest priority according to an order rule. In Java, that rule comes from natural ordering (Comparable) or a supplied Comparator, which is a small object that defines how two values should be ranked. Internally, Java does not keep the whole structure sorted; it only guarantees that the head is the best element.
i, the parent is at (i - 1) / 2, the left child at 2i + 1, and the right child at 2i + 2. This index math is why arrays are such a good fit.offer(), the new element is appended at the end of the array. Then Java performs sift up (also called bubble up): it compares the element with its parent and swaps upward until the heap rule is restored.peek(), Java simply reads index 0. The root of the heap is always the minimum element in a min-heap, so this is constant time.poll(), Java removes the root, moves the last array element into index 0, shrinks the size, and then performs sift down: it compares the new root with its children and swaps with the smaller child until the heap rule is restored.O(n), which is faster than inserting one item at a time.Use a PriorityQueue when you repeatedly need the next smallest or largest item: task scheduling, Dijkstra’s algorithm, event simulation, rate limiting, or top-K problems. Use it when you care about the head, not about scanning everything in order.
| Structure | Ordering | Insert | Head access | Duplicates |
|---|---|---|---|---|
| PriorityQueue | Partial | O(log n) | O(1) | Yes |
| TreeSet | Fully sorted | O(log n) | O(log n) | No |
| Sorted list | Fully sorted | O(n) | O(1) | Yes |
peek(): O(1)offer() and poll(): O(log n), because the element may travel up or down the heap heightcontains() and remove(Object): O(n), because the queue must linearly search for the matching elementoffer(null) throws NullPointerExceptionPriorityBlockingQueueMemory hook for the mechanism: the parent only needs to outrank its children; siblings do not need to be ordered. That one rule is the whole heap.
Real-World Story: In a checkout service, the team used a PriorityQueue to schedule payment retries, fraud checks, and inventory refresh jobs. A bug appeared when engineers iterated the queue with a for-each loop, assuming that iteration would return jobs in priority order. It does not, so urgent payment retries were processed behind low-priority work; users saw checkout timeouts, logs showed jobs handled in a seemingly random order, and retry traffic spiked because the queue was being drained incorrectly.
import java.util.Comparator;
import java.util.Objects;
import java.util.PriorityQueue;
public class Main {
static final class Task {
final String name;
final int priority;
final long seq;
Task(String name, int priority, long seq) {
this.name = name;
this.priority = priority;
this.seq = seq;
}
// Equality matters for remove(Object) and contains(Object), which search for a matching element.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Task)) return false;
Task task = (Task) o;
return priority == task.priority && seq == task.seq && Objects.equals(name, task.name);
}
@Override
public int hashCode() {
return Objects.hash(name, priority, seq);
}
@Override
public String toString() {
return name + "(priority=" + priority + ", seq=" + seq + ")";
}
}
public static void main(String[] args) {
Comparator<Task> byPriorityThenSeq = Comparator
.comparingInt((Task t) -> t.priority)
.thenComparingLong(t -> t.seq);
PriorityQueue<Task> pq = new PriorityQueue<>(byPriorityThenSeq);
pq.offer(new Task("low", 5, 1));
pq.offer(new Task("urgent", 1, 2));
pq.offer(new Task("medium", 3, 3));
pq.offer(new Task("urgent-2", 1, 4));
System.out.println("Peek sees only the head: " + pq.peek());
System.out.println("\nIteration order is NOT priority order:");
for (Task t : pq) {
System.out.println(" " + t);
}
System.out.println("\nPoll order is the real priority order:");
while (!pq.isEmpty()) {
System.out.println(" " + pq.poll());
}
System.out.println("Poll on an empty queue returns: " + pq.poll());
try {
pq.offer(null);
} catch (NullPointerException ex) {
System.out.println("Null rejected: " + ex.getClass().getSimpleName());
}
pq.offer(new Task("A", 2, 5));
pq.offer(new Task("B", 4, 6));
System.out.println("Contains equal object before remove: " + pq.contains(new Task("B", 4, 6)));
System.out.println("remove(Object) with equal object: " + pq.remove(new Task("A", 2, 5)));
System.out.println("remove(Object) for missing element: " + pq.remove(new Task("missing", 1, 7)));
}
}Follow-up & Tricky Questions:
O(log n).O(n). That is better than inserting each element one by one, which would cost O(n log n).Comparator.reverseOrder() for naturally ordered types. Then the largest element becomes the head.peek() throw when empty? No, it returns null; element() throws NoSuchElementException. That difference is a classic interview trap.null? No, it rejects null elements immediately with NullPointerException. The queue uses null internally to represent an empty slot, so null data would break the structure.contains() use the heap order? No, it scans linearly. The heap only gives fast access to the head, not fast lookup by value.Common Mistakes:
poll() a copy.O(1). Correction: peek() is O(1), but offer() and poll() are O(log n).Memory Hook: Think of a family tree where every parent only has to outrank its children; siblings can be messy. That is a heap.
Cheat Sheet:
peek() is O(1); offer() and poll() are O(log n).O(n).Practice Tasks:
Comparator.reverseOrder().