
PriorityQueue in Java
PriorityQueue is a type of queue that stores elements in a way such that the element with the highest priority is always at the front of the queue
July 24, 2024 · 6 min read
A PriorityQueue in Java is a type of queue that stores elements in a way such that the element with the highest priority is always at the front of the queue. It is implemented as a binary heap, providing an efficient way to access and manage elements based on priority.
Key Properties of PriorityQueue
Elements are ordered based on their natural ordering, or by a Comparator supplied at queue construction time.
PriorityQueue is not synchronized. If you need a thread-safe option, use PriorityBlockingQueue instead.
The queue has no fixed size, it can grow as elements are added.
Use PriorityQueue when you need to manage tasks or elements that should be processed in a priority order.
How it actually works
A PriorityQueue is a binary heap stored in a plain Object[], not a tree of node objects. Each element's parent and children are computed by index arithmetic — the children of index i live at 2i + 1 and 2i + 2, and the parent of i lives at (i - 1) / 2. offer() appends to the end and "sifts up" (swapping with its parent until the heap property holds); poll() removes index 0 (always the smallest/highest-priority element under the given ordering), moves the last element to the root, and "sifts down." Both are O(log n); peek() is O(1) since the top element is always at index 0.
Iterating does not give you priority order
This is the single most common PriorityQueue surprise: only poll() (or repeated peek()+poll()) guarantees elements come out in priority order. The Iterator returned by iterator() walks the backing array in whatever order the heap happens to store it in — which satisfies the heap property (a parent is always ≤ its children) but is not a fully sorted order.
PriorityQueue<Integer> pq = new PriorityQueue<>(List.of(5, 1, 3, 2, 4));
System.out.print("Iterator order: ");
for (int n : pq) {
System.out.print(n + " ");
}
System.out.print("\nPoll order: ");
while (!pq.isEmpty()) {
System.out.print(pq.poll() + " ");
}Iterator order: 1 2 3 5 4
Poll order: 1 2 3 4 5If you need a fully sorted snapshot without draining the queue, sort a copy: Arrays.sort(pq.toArray()) or pq.stream().sorted().toList().
The iterator is still fail-fast for structural changes made outside the iterator itself — it just isn't ordered.
Mutating priority after insertion breaks the heap
Same category of bug as TreeSet's compareTo trap: if you change a field your Comparator (or compareTo) depends on after the element is already in the queue, the heap's internal ordering invariant silently breaks — poll() can start returning elements out of order, because the heap was built around a priority that no longer matches. If a task's priority needs to change, remove it (remove(task) — O(n), since it has to search first) and re-add it.
Example: Suppose you are developing a task management system where tasks have different priorities. Higher-priority tasks should be processed before lower-priority tasks.
class Task {
private String name;
private int priority;
public Task(String name, int priority) {
this.name = name;
this.priority = priority;
}
public String getName() {
return name;
}
public int getPriority() {
return priority;
}
@Override
public String toString() {
return "Task{name='" + name + "', priority=" + priority + "}";
}
}
public class TaskManager {
public static void main(String[] args) {
PriorityQueue<Task> taskQueue = new PriorityQueue<>(Comparator.comparingInt(Task::getPriority).reversed());
taskQueue.add(new Task("Task 1", 3));
taskQueue.add(new Task("Task 2", 1));
taskQueue.add(new Task("Task 3", 4));
taskQueue.add(new Task("Task 4", 2));
System.out.println("Printing tasks based on priority:");
while (!taskQueue.isEmpty()) {
Task task = taskQueue.poll();
System.out.println("Processing " + task);
}
}
}Output:
Printing tasks based on priority:
Processing Task{name='Task 3', priority=4}
Processing Task{name='Task 1', priority=3}
Processing Task{name='Task 4', priority=2}
Processing Task{name='Task 2', priority=1}PriorityQueue vs the Alternatives
- vs
TreeSet/TreeMap: both are O(log n) for insert, butPriorityQueueonly gives you efficient access to the single smallest/largest element. Finding or removing an arbitrary element is O(n) in aPriorityQueue(it has to scan the array) versus O(log n) in aTreeSet. If you only ever need "give me the next one,"PriorityQueueis lighter — array-backed, no per-node allocation, better cache locality. If you need range queries or arbitrary removal, useTreeSet. - vs sorting a
Listrepeatedly: don't. Sorting is O(n log n) every time; aPriorityQueueamortizes that cost across individual O(log n) insertions, which wins as soon as you're doing more than one insert between reads. - vs
PriorityBlockingQueue: same heap semantics, but thread-safe and blocking —take()waits if the queue is empty instead of returningnull. Reach for it when producers and consumers run on different threads.
