RadhaPublication
HomeInterviewLearning PathsRole TracksMockBooksPricing
Login / Sign UpLog in

© 2026 Radha Publication. All rights reserved.

Shop on AmazonDonateContactPrivacyTerms
Java questions
HardJava#1036 min readJul 11, 2026

How is PriorityQueue implemented internally in Java?

practice
learning
Practice modeTest yourself instead of reading straight through

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.

🧠 Memory Map
Memory map — visual summary of this topic

What it is

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.

How it works under the hood

  1. The queue stores elements in an internal array.
  2. The array is shaped like a complete binary tree (all levels filled left to right except maybe the last).
  3. The element at index 0 is the head of the heap, so it is the smallest element in a min-heap.
  4. When you call offer(), the new element is placed at the end of the array.
  5. Then Java performs heapify-up (also called sift-up): it compares the new element with its parent and swaps upward until the heap property is restored.
  6. When you call 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.
  7. peek() simply reads the root element, so it does not need rearrangement.

Why this design is used

  • Fast best-item access: the top element is always available in O(1).
  • Efficient updates: insert and remove both stay around O(log n), which is much better than sorting after each change.
  • Space-efficient: the heap sits in one array, so it is cache-friendly and simple.

Complexity and practical details

  • peek() = O(1)
  • offer() / add() = O(log n)
  • poll() = O(log n)
  • remove(Object) = O(n) because Java must search for the element first
  • Default ordering is natural ordering, so elements must be mutually comparable unless you provide a Comparator

On 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.

Comparison with alternatives

StructureHead accessInsertFull order?Typical use
PriorityQueueO(1)O(log n)NoNext-best item
TreeSetO(log n)O(log n)YesSorted unique items
ArrayList + sortO(1)O(1)Only after sortBatch sorting

Important edge cases

  1. Null is not allowed. Adding null throws NullPointerException.
  2. Iterator order is not priority order. If you iterate, you see heap order, not sorted order.
  3. Duplicates are allowed. A priority queue does not enforce uniqueness.
  4. Comparator must be consistent enough. A broken comparator can cause strange ordering, though the structure still tries to maintain the heap rule.
  5. Not thread-safe. If multiple threads access it, use external synchronization or a concurrent alternative.

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.

Java
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:

  • How does Java keep the heap property after insertion and removal? It uses sift-up after insertion and sift-down after removal, swapping only along one root-to-leaf path, which keeps operations at O(log n).
  • Why is iterating over a PriorityQueue not sorted? Because the internal array only guarantees the heap rule, not full ordering. Only the head element is guaranteed to be the minimum or maximum depending on the comparator.
  • What is the difference between 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.
  • Can 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.
  • When would you choose 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.
  • Tricky: Does 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).
  • Tricky: Is PriorityQueue thread-safe? No. If multiple threads modify it, you need external synchronization or a concurrent queue designed for that use case.

Common Mistakes:

  • Thinking it is fully sorted. Correction: only the head is guaranteed to be in priority order; the rest is just heap-shaped.
  • Using it for ordered iteration. Correction: if you need sorted traversal, remove elements one by one or choose a sorted structure like TreeSet.
  • Forgetting null handling. Correction: PriorityQueue rejects null with NullPointerException.
  • Assuming all operations are logarithmic. Correction: 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:

  • Backed by an array-based binary heap.
  • Default is a min-heap.
  • peek() = O(1), offer() / poll() = O(log n).
  • Iteration order is not sorted.
  • Duplicates allowed, null forbidden.
  • Use a Comparator for custom ordering or max-heap behavior.

Practice Tasks:

  • Write a PriorityQueue of integers and print elements in ascending order using poll().
  • Change it into a max-heap using Comparator.reverseOrder() and verify the output.
  • Create a custom Task class with priority and timestamp, then order tasks by priority first and older timestamp second.
Previous
Back to Questions←→to navigate
Next

Recommended Resources

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

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()); } } }