Why this matters
ArrayDequeis the correct replacement for bothStackandLinkedList, and most code has not caught up.Queueoffers two methods for every operation — one throwing, one returning a sentinel — and picking the wrong one produces either noisy exceptions or silently ignored failures.PriorityQueue's iteration order surprises nearly everyone the first time they print one.
Queue and Deque
Iteration order is not sorted — only polling is.
Queueis first-in-first-out. You add at the tail and remove from the head.Deque— double-ended queue — allows adding and removing at both ends, so it serves as a queue and a stack.ArrayDequeis a circular array implementation. It is the right default for both roles.PriorityQueueis a binary heap; the head is always the smallest element by the ordering, not the oldest.
Two methods for every operation
Each queue operation comes in a throwing form and a sentinel-returning form.
| Aspect | Operation | Throwing vs returning |
|---|---|---|
| Add to tail | add(e) — throws if capacity-bound and full | offer(e) — returns false |
| Remove from head | remove() — throws NoSuchElementException | poll() — returns null |
| Inspect head | element() — throws NoSuchElementException | peek() — returns null |
Add to tail
Operationadd(e) — throws if capacity-bound and fullThrowing vs returningoffer(e) — returns falseRemove from head
Operationremove() — throws NoSuchElementExceptionThrowing vs returningpoll() — returns nullInspect head
Operationelement() — throws NoSuchElementExceptionThrowing vs returningpeek() — returns null
Prefer the sentinel forms. An empty queue is a normal state, not an error.
Queue<String> tasks = new ArrayDeque<>();
tasks.offer("compile");
tasks.offer("test");
tasks.offer("deploy");
System.out.println(tasks.peek()); // compile — look without removing
System.out.println(tasks.poll()); // compile — removed
System.out.println(tasks.size()); // 2
while (!tasks.isEmpty()) {
System.out.println("Running " + tasks.poll());
}
System.out.println(tasks.poll()); // null — no exception
ArrayDeque as a stack
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); // addFirst
stack.push(2);
stack.push(3);
System.out.println(stack.peek()); // 3 — peekFirst
System.out.println(stack.pop()); // 3 — removeFirst
System.out.println(stack); // [2, 1] — head first, so top first
Because push and pop both work at the head, last-in-first-out falls out naturally.
| Aspect | ArrayDeque | java.util.Stack |
|---|---|---|
| Backing structure | Circular array | Vector — synchronised |
| Locking | None | Every method, even single-threaded |
| Indexed access | Not exposed | Inherited from Vector — a design leak |
| Iteration order | Head to tail, so top first | Bottom to top — the reverse of what you expect |
| Recommended | Yes | No — legacy since Java 1.0 |
Backing structure
ArrayDequeCircular arrayjava.util.StackVector — synchronisedLocking
ArrayDequeNonejava.util.StackEvery method, even single-threadedIndexed access
ArrayDequeNot exposedjava.util.StackInherited from Vector — a design leakIteration order
ArrayDequeHead to tail, so top firstjava.util.StackBottom to top — the reverse of what you expectRecommended
ArrayDequeYesjava.util.StackNo — legacy since Java 1.0
Stack's iteration order being bottom-to-top is a genuine trap in otherwise correct-looking code.
PriorityQueue
Elements come out in priority order, smallest first by default. It is a binary heap, not a sorted list.
PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.addAll(List.of(50, 10, 40, 20, 30));
System.out.println(queue.peek()); // 10 — the smallest
while (!queue.isEmpty()) {
System.out.print(queue.poll() + " "); // 10 20 30 40 50
}
// Largest first
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
// Ordered by a field, with a tie-breaker
PriorityQueue<Task> byPriority = new PriorityQueue<>(
Comparator.comparingInt(Task::priority)
.thenComparing(Task::createdAt));
Costs
offerandpollare O(log n) — the heap sifts the element up or down.peekis O(1).containsandremove(Object)are O(n) — there is no index to search by.- It is unbounded and grows as needed, and it rejects
null.
A representative use: keep the k largest items from a large stream using a min-heap of size k.
PriorityQueue<Integer> topK = new PriorityQueue<>(); // min-heap
for (int value : hugeStream) {
topK.offer(value);
if (topK.size() > k) {
topK.poll(); // drop the smallest, keeping the k largest
}
}
This uses O(k) memory rather than sorting the whole input, which is the standard answer to "top k elements" problems.
Blocking queues
For producer-consumer work across threads, java.util.concurrent adds queues that block instead of
returning null.
BlockingQueue<Task> pipeline = new LinkedBlockingQueue<>(100); // bounded
// Producer: waits if the queue is full
pipeline.put(task);
// Consumer: waits if the queue is empty
Task next = pipeline.take();
// With a timeout, when waiting forever is not acceptable
Task maybe = pipeline.poll(5, TimeUnit.SECONDS); // null if nothing arrives
The main variants
ArrayBlockingQueue— fixed capacity, a single lock, backed by an array.LinkedBlockingQueue— optionally bounded, separate locks for head and tail so producers and consumers contend less.PriorityBlockingQueue— unbounded, ordered by priority.SynchronousQueue— zero capacity; eachputwaits for a matchingtake. A direct hand-off.DelayQueue— elements only become available once their delay expires. Useful for scheduling.
Common misreadings
- "
LinkedListis the queue implementation." It implementsDeque, butArrayDequeis faster for every queue and stack operation. - "
Stackis fine for a stack." Its iteration order is bottom-to-top and every method is synchronised. UseArrayDeque. - "A
PriorityQueueis sorted." Only the head is guaranteed. Iteration reflects the heap array. - "
addandofferare the same." For unbounded queues they behave alike, butofferreturnsfalsewhereaddthrows. - "
pollon an empty queue throws." It returnsnull;remove()is the throwing form. - "
ArrayDequeaccepts nulls." It does not, becausenullis the sentinel meaning "empty".
Quick recall
Everything you need if you only revisit this box.
Queueis FIFO at the ends;Dequeworks at both ends and serves as a stack too.- Prefer the sentinel methods —
offer,poll,peek— overadd,remove,element. ArrayDequereplaces bothStackandLinkedList.Stackis synchronised and iterates bottom-to-top.PriorityQueueis a binary heap: O(log n) offer and poll, O(1) peek, O(n) contains, and iteration is not sorted.- A size-k min-heap solves "top k" in O(k) memory.
- Blocking queues make producer-consumer work safe:
putandtakeblock, andpollaccepts a timeout. - Bound your queues — an unbounded queue in front of a slow consumer hides the problem until the heap runs out.
- Neither
ArrayDequenorPriorityQueueacceptsnull.
Test yourself
Answer these before moving on — recall is what makes it stick.