Question: How is PriorityQueue implemented internally in Java?
Answer: Java’s PriorityQueue is backed by a binary heap, which is a tree stored inside an array. The smallest element is kept at the top by default, so peek() is fast and poll() removes the top element efficiently. It does not keep the whole queue fully sorted; it only guarantees that the head is the highest-priority item.
Interview-Ready Answer: I’d say Java PriorityQueue uses a binary heap under the hood, stored in an array. By default it is a min-heap, so the smallest element comes out first. Insertion and removal both take O(log n), while peek() is O(1). A nice detail to mention is that iteration is not sorted order, because the heap only maintains the top element, not a fully ordered list.
Detailed Explanation: Think of a priority queue as a “best comes out first” line. In Java, the default “best” is the smallest element, unless you give a custom Comparator. The important mental model is this: a PriorityQueue is not a sorted list; it is a heap that makes finding the next best item cheap.
0 is the head of the heap, so it is the smallest element in a min-heap.offer(), the new element is placed at the end of the array.poll(), Java removes the root element, moves the last element to index 0, and then performs heapify-down (sift-down) to push it into the right place.peek() simply reads the root element, so it does not need rearrangement.O(1).O(log n), which is much better than sorting after each change.peek() = O(1)offer() / add() = O(log n)poll() = O(log n)remove(Object) = O(n) because Java must search for the element firstComparatorOn current Java versions, the default initial capacity is 11. As the queue grows, the backing array expands automatically. This is a good interview detail because it shows you know the implementation is array-based, not node-based like a linked structure.
| Structure | Head access | Insert | Full order? | Typical use |
|---|---|---|---|---|
| PriorityQueue | O(1) | O(log n) | No | Next-best item |
| TreeSet | O(log n) | O(log n) | Yes | Sorted unique items |
| ArrayList + sort | O(1) | O(1) | Only after sort | Batch sorting |
null throws NullPointerException.Memory hook: Imagine a family tree where only the “most urgent person” is kept at the front door. Everyone else is in a neatly arranged waiting area. The house is not fully alphabetized — only the next person to serve is easy to reach.
Real-World Story: In a ride-hailing dispatch service, the system may need to pick the nearest or highest-priority driver request first. A PriorityQueue is a natural fit because the dispatcher keeps inserting new requests and repeatedly taking the best one. The queue does not need every request fully sorted; it only needs the top candidate immediately.
If someone misunderstands the heap behavior and assumes iteration is sorted, a scheduler bug can appear: the code logs the queue and thinks requests are processed in the wrong order, then “fixes” it by sorting on every loop. That can spike CPU usage, increase latency, and make the dispatcher slower during rush hour. In logs, you might see repeated reordering, growing response times, and complaints like “high-priority drivers are still waiting.” The real issue is often not the queue itself, but using it as if it were a sorted list.
import java.util.Comparator;
import java.util.NoSuchElementException;
import java.util.PriorityQueue;
public class PriorityQueueInternalDemo {
public static void main(String[] args) {
// Default PriorityQueue is a min-heap: smallest value comes out first.
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(40);
pq.offer(10);
pq.offer(30);
pq.offer(20);
pq.offer(10); // duplicates are allowed
System.out.println("peek() = " + pq.peek()); // O(1): smallest element
System.out.print("poll order: ");
while (!pq.isEmpty()) {
System.out.print(pq.poll() + " "); // O(log n) each
}
System.out.println();
// Edge case 1: null is not allowed.
try {
pq.offer(null);
} catch (NullPointerException e) {
System.out.println("Adding null fails: " + e.getClass().getSimpleName());
}
// Edge case 2: iteration is NOT sorted order.
PriorityQueue<Integer> unsortedView = new PriorityQueue<>();
unsortedView.offer(5);
unsortedView.offer(1);
unsortedView.offer(4);
unsortedView.offer(2);
unsortedView.offer(3);
System.out.print("iterator order: ");
for (Integer x : unsortedView) {
System.out.print(x + " ");
}
System.out.println();
System.out.print("poll order again: ");
while (!unsortedView.isEmpty()) {
System.out.print(unsortedView.poll() + " ");
}
System.out.println();
// Custom Comparator: turn it into a max-heap.
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
maxHeap.offer(40);
maxHeap.offer(10);
maxHeap.offer(30);
maxHeap.offer(20);
System.out.println("maxHeap.peek() = " + maxHeap.peek()); // largest element first
System.out.print("maxHeap poll order: ");
while (!maxHeap.isEmpty()) {
System.out.print(maxHeap.poll() + " ");
}
System.out.println();
// Edge case 3: poll() on empty queue returns null, not an exception.
System.out.println("poll on empty = " + pq.poll());
// remove() on empty queue can throw if you call remove() with no arguments.
try {
System.out.println("remove() on empty = " + pq.remove());
} catch (NoSuchElementException e) {
System.out.println("remove() on empty fails: " + e.getClass().getSimpleName());
}
}
}
Follow-up & Tricky Questions:
O(log n).peek() and poll()? peek() reads the head without removal, while poll() removes and returns it. That means peek() is O(1), but poll() needs reheapification.PriorityQueue store duplicates and custom objects? Yes, duplicates are allowed. For custom objects, you must supply either a Comparator or implement Comparable so Java knows how to order them.TreeSet instead? Use TreeSet when you need items kept fully sorted and also want uniqueness. Use PriorityQueue when you only care about repeatedly extracting the next best item.remove(Object) stay O(log n)? No. It first searches linearly for the object, so the overall cost is O(n), even though the reheapification step afterward is O(log n).PriorityQueue thread-safe? No. If multiple threads modify it, you need external synchronization or a concurrent queue designed for that use case.Common Mistakes:
TreeSet.PriorityQueue rejects null with NullPointerException.peek() is constant time, and remove(Object) is linear search plus heap repair.Memory Hook: “The front of the line is guaranteed; the rest of the line is only heap-organized.” That is the whole mental model.
Cheat Sheet:
peek() = O(1), offer() / poll() = O(log n).null forbidden.Comparator for custom ordering or max-heap behavior.Practice Tasks:
PriorityQueue of integers and print elements in ascending order using poll().Comparator.reverseOrder() and verify the output.Task class with priority and timestamp, then order tasks by priority first and older timestamp second.