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.
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
| Scenario | Cause | Result |
|---|---|---|
NoSuchElementException | Calling remove() / get() on empty queue | Runtime crash |
NullPointerException | Adding null to a queue that does not allow nulls | Runtime crash |
IllegalStateException | Capacity-bounded queue is full | Runtime crash or blocking |
ClassCastException | Elements not mutually comparable in PriorityQueue | Runtime crash |
Trade-Off Table
| Implementation | Insert | Remove Head | Peek | Thread Safety |
|---|---|---|---|---|
ArrayDeque | O(1) | O(1) | O(1) | None |
LinkedList | O(1) | O(1) | O(1) | None |
PriorityQueue | O(log n) | O(log n) | O(1) | None |
ArrayBlockingQueue | O(1) | O(1) | O(1) | Yes (bounded) |
LinkedBlockingQueue | O(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()vsremove()failures —poll()returningnullis often silently ignored - Profile
PriorityQueueoperations for O(n) instead of O(log n) — indicates a broken comparator - Log
IllegalStateExceptionor block events on bounded queues - Monitor for memory pressure from unbounded queue growth
Security Notes
QueueandDequeare not thread-safe by default; use blocking queue implementations for concurrent accessPriorityQueueis not thread-safe; usePriorityBlockingQueuefor concurrent priority queue access- Bounded queues prevent memory exhaustion from producer-consumer imbalances
- When serializing queues, be aware that
ArrayDequeserialization only captures elements, not internal state
Common Pitfalls / Anti-Patterns
poll()vsremove():poll()returnsnullon empty;remove()throwsNoSuchElementException. Usingremove()without checkingisEmpty()is a bug.peek()vselement(): Same distinction —peek()returnsnullon empty;element()throws- PriorityQueue is not sorted: The queue order is heap order, not sorted order. Only
peek()always returns the minimum. Iterating aPriorityQueuedoes NOT yield elements in sorted order. nullelements:PriorityQueueandArrayDequedo not allownullelements;LinkedListallowsnulladd()vsoffer(): Both insert if capacity allows, butadd()throwsIllegalStateExceptionon a full bounded queue whileoffer()returnsfalse
Quick Recap
Queue= FIFO,Deque= both ends,PriorityQueue= priority-orderedArrayDequeis the most efficient general-purpose Deque — use it instead ofLinkedListfor queue/deque use casesPriorityQueueis a min-heap —peek()returns smallest; use a reversed comparator for max-heappoll()/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
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."
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)."
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."
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."
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."
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."
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)."
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())`."
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."
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())`."
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."
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."
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."
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())`."
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."
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."
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."
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."
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."
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
- Oracle Queue Documentation — Official Queue interface specification
- Oracle Deque Documentation — Official Deque interface specification
- Baeldung: Java Queue Types — Comprehensive guide to Queue types and their characteristics
- PriorityQueue vs TreeMap — When to use each for ordered data
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.
Arithmetic Operators in Java
Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.
Array Basics in Java
Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.