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.
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
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
- ArrayList — ArrayList as a List vs Queue usage patterns
- LinkedList — LinkedList implements Queue/Deque
- HashSet — sets vs queues for different data structures
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.