Queue and Deque in Java

Master Java queues: FIFO ordering, Deque for double-ended operations, PriorityQueue for heap-based ordering, and when to use each.

published: reading time: 13 min read author: GeekWorkBench

Queue and Deque in Java

Java’s Queue interface models a FIFO (first-in-first-out) data structure, while Deque extends it to support operations at both ends. PriorityQueue implements a queue where elements are ordered by a priority value rather than insertion order, backed by a binary heap.

Introduction

Queues and deques are the bridges between ad-hoc data storage and structured processing patterns. The Queue interface in Java models a FIFO (first-in-first-out) data structure — elements are added at the tail and removed from the head, just like a line at a coffee shop. The Deque interface extends Queue to support operations at both ends, meaning the same structure can function as both a queue (FIFO) and a stack (LIFO) depending on which methods you call. PriorityQueue breaks from the FIFO ordering entirely, maintaining elements in heap order based on a priority value — the smallest element (by natural ordering or a custom comparator) is always at the front.

Choosing the right queue implementation has real consequences for performance and correctness. ArrayDeque is the workhorse — it provides O(1) amortized operations at both head and tail with excellent cache locality, outperforming LinkedList for queue and deque use cases despite both having the same algorithmic complexity. LinkedList requires per-element object allocation and pointer dereferencing on each operation. PriorityQueue has O(log n) insertion and removal but O(1) peek, and critically — the queue order is heap order, not sorted order. Iterating a PriorityQueue does not yield elements in sorted sequence; only poll() does.

This post covers when to use Queue versus Deque versus PriorityQueue, the API symmetry between add()/remove() (which throw on failure) and offer()/poll() (which return null/false on failure), and why peek()/element() have the same distinction. It explains the min-heap internals of PriorityQueue, how to create a max-heap with a reversed comparator, and why null elements are not allowed in PriorityQueue or ArrayDeque. It also covers thread safety — none of the standard implementations are thread-safe — and the blocking queue variants (LinkedBlockingQueue, ArrayBlockingQueue) for concurrent producer-consumer patterns.

When to Use Queue

Use Queue when:

  • You need to process elements in the order they were added (FIFO)
  • You are implementing breadth-first search, task scheduling, or producer-consumer patterns
  • You need to buffer elements between a producer and consumer with different processing rates

Do not use Queue when:

  • You need to access elements by index (use List)
  • Order should be based on element priority rather than insertion order
  • You need LIFO (stack) behavior — use a Deque instead

When to Use Deque

Use Deque when:

  • You need to add or remove from both the head and tail
  • You are implementing a double-ended queue, stack, or both simultaneously
  • You need efficient head/tail operations with O(1) performance

PriorityQueue

PriorityQueue is a min-heap by default — the element with the smallest value according to natural ordering (or a custom comparator) is at the front. Elements are NOT sorted on insertion; the heap property is maintained during each add() and poll() operation.

Mermaid Diagram: Queue Family

graph TD
    A["Queue Interface"] --> B["Deque Interface"]
    B --> C["ArrayDeque"]
    B --> D["LinkedList"]
    A --> E["PriorityQueue"]
    A --> F["BlockingQueue implementations"]
    C --> G["Resizable array<br/>O(1) head/tail<br/>Not thread-safe"]
    D --> H["Doubly linked list<br/>O(1) all ends<br/>Not thread-safe"]
    E --> I["Binary heap<br/>O(log n) insert/remove<br/>Not sorted order"]
    F --> J["Concurrent access<br/>e.g., LinkedBlockingQueue"]

Failure Scenarios

ScenarioCauseResult
NoSuchElementExceptionCalling remove() / get() on empty queueRuntime crash
NullPointerExceptionAdding null to a queue that does not allow nullsRuntime crash
IllegalStateExceptionCapacity-bounded queue is fullRuntime crash or blocking
ClassCastExceptionElements not mutually comparable in PriorityQueueRuntime crash

Trade-Off Table

ImplementationInsertRemove HeadPeekThread Safety
ArrayDequeO(1)O(1)O(1)None
LinkedListO(1)O(1)O(1)None
PriorityQueueO(log n)O(log n)O(1)None
ArrayBlockingQueueO(1)O(1)O(1)Yes (bounded)
LinkedBlockingQueueO(1)O(1)O(1)Yes (optional bound)

Code Snippets

ArrayDeque as Stack and Queue

Deque<String> stack = new ArrayDeque<>();
stack.push("first");
stack.push("second");
System.out.println(stack.pop());  // "second"

Deque<String> queue = new ArrayDeque<>();
queue.addLast("task1");
queue.addLast("task2");
System.out.println(queue.removeFirst());  // "task1"

PriorityQueue with Custom Comparator

// Max-heap via reversed comparator
PriorityQueue<int[]> maxHeap = new PriorityQueue<>(
    (a, b) -> Integer.compare(b[1], a[1])  // Compare by second element, descending
);

maxHeap.add(new int[]{1, 5});
maxHeap.add(new int[]{2, 9});
maxHeap.add(new int[]{3, 3});

System.out.println(maxHeap.peek()[1]);  // 9 — highest priority
System.out.println(maxHeap.poll()[1]);   // 9

Producer-Consumer Pattern

Queue<Integer> buffer = new LinkedList<>();
buffer.add(1);
buffer.add(2);
buffer.add(3);

while (!buffer.isEmpty()) {
    int task = buffer.poll();  // removeFirst() — returns null if empty
    process(task);
}

Observability Checklist

  • Monitor queue depth to detect producer-consumer imbalances
  • Track poll() vs remove() failures — poll() returning null is often silently ignored
  • Profile PriorityQueue operations for O(n) instead of O(log n) — indicates a broken comparator
  • Log IllegalStateException or block events on bounded queues
  • Monitor for memory pressure from unbounded queue growth

Security Notes

  • Queue and Deque are not thread-safe by default; use blocking queue implementations for concurrent access
  • PriorityQueue is not thread-safe; use PriorityBlockingQueue for concurrent priority queue access
  • Bounded queues prevent memory exhaustion from producer-consumer imbalances
  • When serializing queues, be aware that ArrayDeque serialization only captures elements, not internal state

Common Pitfalls / Anti-Patterns

  1. poll() vs remove(): poll() returns null on empty; remove() throws NoSuchElementException. Using remove() without checking isEmpty() is a bug.
  2. peek() vs element(): Same distinction — peek() returns null on empty; element() throws
  3. PriorityQueue is not sorted: The queue order is heap order, not sorted order. Only peek() always returns the minimum. Iterating a PriorityQueue does NOT yield elements in sorted order.
  4. null elements: PriorityQueue and ArrayDeque do not allow null elements; LinkedList allows null
  5. add() vs offer(): Both insert if capacity allows, but add() throws IllegalStateException on a full bounded queue while offer() returns false

Quick Recap

  • Queue = FIFO, Deque = both ends, PriorityQueue = priority-ordered
  • ArrayDeque is the most efficient general-purpose Deque — use it instead of LinkedList for queue/deque use cases
  • PriorityQueue is a min-heap — peek() returns smallest; use a reversed comparator for max-heap
  • poll() / offer() return null/false on failure; remove() / add() throw on failure
  • None of the standard implementations are thread-safe; use blocking queue implementations for concurrent producers/consumers

Interview Questions

1. What is the difference between a Queue and a Deque?

Model Answer: "`Queue` is a **FIFO** (first-in-first-out) interface where you add at the tail and remove from the head. `Deque` (double-ended queue) extends `Queue` and allows adding and removing from **both ends**. This means a `Deque` can function as both a FIFO queue and a LIFO stack with O(1) operations at both ends."

2. How does PriorityQueue maintain order?

Model Answer: "`PriorityQueue` implements a **binary min-heap** stored in a balanced tree structure inside an array. The heap property ensures the smallest element (by natural ordering or comparator) is always at index 0, reachable by `peek()` in O(1). When `poll()` removes the root, it replaces it with the last element and **sifts down** to restore the heap property — O(log n). Insertions (`offer()`) **sift up** — also O(log n)."

3. Why is ArrayDeque preferred over LinkedList for queue/deque operations?

Model Answer: "`ArrayDeque` provides **O(1)** amortized operations at both ends with better **cache locality** (contiguous memory). `LinkedList` provides the same O(1) complexity but each operation requires pointer dereferencing and object allocation per element. For queue/deque use cases (not indexed access), `ArrayDeque` has lower memory overhead and better performance in practice."

4. What is the time complexity of PriorityQueue's operations?

Model Answer: "`offer()` (insert) and `poll()` (remove min) are **O(log n)**. `peek()` (read min) is **O(1)**. Unlike a sorted array, inserting n elements costs O(n log n) total, not O(n), because each insertion costs log n. However, this is still more efficient than sorting after all insertions."

5. When would you use a BlockingQueue over a regular Queue?

Model Answer: "Use a `BlockingQueue` when you have **concurrent producer-consumer threads**. `BlockingQueue` operations block the calling thread when the queue is full (on `put()`) or empty (on `take()`), providing built-in coordination between threads. Standard `Queue` implementations throw or return null/false on failure, requiring manual handling. `LinkedBlockingQueue` and `ArrayBlockingQueue` are the main implementations."

6. What is the difference between `add()`, `offer()`, and `offer(e, timeout, unit)`?

Model Answer: "All three insert if capacity allows. `add()` throws `IllegalStateException` when full. `offer()` returns `false` when full. `offer(e, timeout, unit)` blocks for the specified timeout before returning false — useful when you want to handle full-queue conditions gracefully in concurrent code."

7. How does PriorityQueue handle element ordering during `poll()`?

Model Answer: "On `poll()`, the root (minimum element) is removed, replaced by the last element, then **sifted down** to restore the heap property — comparing the replacement with its children and swapping if out of order, repeating until the heap property is restored. This is O(log n)."

8. What is a max-heap and how do you create one in Java?

Model Answer: "A max-heap stores the largest element at the root (opposite of PriorityQueue's default min-heap). Create one in Java by passing a **reversed comparator** to the PriorityQueue constructor: `new PriorityQueue<>(Comparator.reverseOrder())`. For primitive arrays, use `new PriorityQueue<>(Collections.reverseOrder())`."

9. Why is ArrayDeque preferred over LinkedList for stack operations?

Model Answer: "ArrayDeque provides **O(1) amortized** push/pop at both ends with **better cache locality** (contiguous memory). LinkedList requires allocating a new node object per element and pointer dereferencing on each operation. ArrayDeque has no per-element allocation overhead and is faster in practice for queue/deque/stack workloads."

10. What happens when you iterate a PriorityQueue?

Model Answer: "Iterating a PriorityQueue does **not** yield elements in sorted order. The iterator traverses the internal array in heap order (level-order), not sorted order. If you need sorted order, drain the queue via repeated `poll()` calls, or use `Arrays.sort(pq.toArray())`."

11. Can ArrayDeque grow and shrink?

Model Answer: "ArrayDeque grows automatically (doubles in size) when needed — no explicit resizing method is exposed. It does **not** automatically shrink when elements are removed. If memory is a concern after bulk removals, there is no `trimToSize()` equivalent; you would need to create a new smaller ArrayDeque and copy elements."

12. What is the difference between `addFirst()` and `push()` in ArrayDeque?

Model Answer: "Both insert at the head, but `push()` is modeled after the Stack interface and throws `NoSuchElementException` if insertion fails (when capacity is fixed and bounded). `addFirst()` returns `boolean` (true if added, false if not). For unbounded ArrayDeque, both behave the same way."

13. How does `LinkedBlockingQueue` differ from `LinkedList` in queue implementations?

Model Answer: "`LinkedBlockingQueue` is **thread-safe** and optionally bounded (can have max capacity). It supports concurrent producers/consumers with blocking `put()` and `take()` operations. `LinkedList` (used via `new LinkedList<>()`) is not thread-safe and has no capacity limit by default. Choose based on whether thread safety is needed."

14. What is the time complexity of iterating over a PriorityQueue?

Model Answer: "Iterating a `PriorityQueue` yields elements in heap order (level-order of the internal tree), **not** sorted order. The iterator traverses the internal array in physical order, not in priority order. If you need sorted order, repeatedly call `poll()` to extract in sorted fashion, or use `Arrays.sort(pq.toArray())`."

15. When should you use `ArrayDeque` over `LinkedList` for Deque operations?

Model Answer: "Use `ArrayDeque` for all typical queue/deque/stack workloads unless you need `ListIterator` bidirectional traversal. `ArrayDeque` provides O(1) amortized operations with better cache locality (contiguous memory). `LinkedList` requires per-element node allocation and pointer dereferencing on each operation."

16. What is the purpose of `Comparator.reverseOrder()` in PriorityQueue?

Model Answer: "`new PriorityQueue<>(Comparator.reverseOrder())` creates a **max-heap** (largest element at the front) instead of the default min-heap. The comparator reverses the natural ordering so that `peek()` returns the maximum element rather than the minimum."

17. How does a bounded queue prevent memory exhaustion in producer-consumer patterns?

Model Answer: "A bounded queue (e.g., `ArrayBlockingQueue(capacity)`) blocks the producer thread when full and blocks the consumer when empty. This back-pressure prevents unbounded memory growth when producers generate faster than consumers can process. Set capacity based on acceptable memory usage and consumer processing rate."

18. What happens when you call `take()` on an empty blocking queue?

Model Answer: "`take()` blocks the calling thread indefinitely until an element becomes available. This is different from `poll()` which returns `null` immediately, or `poll(timeout, unit)` which blocks only for the specified duration. Use `take()` when it's acceptable to wait indefinitely for work."

19. What is the difference between `offer(e)` and `add(e)` in bounded queues?

Model Answer: "Both insert an element if capacity allows. `add()` throws `IllegalStateException` when the queue is full. `offer()` returns `false` when full. For bounded queues, `offer()` is preferred since it provides a non-exception return value for handling the full state."

20. How does `PriorityQueue` handle elements that are not mutually comparable?

Model Answer: "`PriorityQueue` throws `ClassCastException` at insertion time if elements cannot be compared. This occurs when the queue contains elements that don't implement `Comparable` and no `Comparator` was provided at construction, or when the comparator rejects the pair. The exception fires during the sift-up operation after insertion."

Further Reading

Conclusion

Queue and Deque are the bridges between array-based storage and practical processing patterns. Queue gives you FIFO semantics for task scheduling and breadth-first traversal, while Deque extends that to both ends — enabling efficient stacks, queues, and double-ended operations in a single structure.

ArrayDeque is the workhorse implementation: O(1) at both ends with better cache locality than LinkedList for queue and deque use cases. PriorityQueue breaks from FIFO entirely, ordering elements by a priority value using a binary heap. This is essential for Dijkstra-style algorithms, task prioritization, and anywhere insertion order does not equal processing order.

Queue and Deque patterns connect naturally to LinkedList, which also implements Deque and serves as the underlying structure for many queue patterns. For sorted elements with O(log n) operations, TreeSet is worth a look.

Category

Related Posts

Abstract Classes in Java

Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.

#java-abstract-classes #java #java-fundamentals

Arithmetic Operators in Java

Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.

#java-arithmetic-operators #java #java-fundamentals

Array Basics in Java

Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.

#java-array-basics #java #java-fundamentals