Queue Interface
The Queue interface in java.util represents a collection designed for holding elements prior to processing, following a defined ordering policy. Unlike a List which provides random access by index, a Queue exposes only the head element and enforces a particular insertion and removal discipline. The standard Queue contract is FIFO — first in, first out — though subinterfaces and implementations may define alternative orderings such as priority order or LIFO. Understanding the Queue interface means understanding its dual-form API (throwing vs non-throwing methods), its place in the Collections Framework hierarchy, the semantics of each operation, and when a Queue is the appropriate data structure over a List or Deque. This entry covers the Queue contract in depth, the method pairs and when to use each, the major implementations, and the design patterns that queues enable.
The Queue Contract and FIFO Ordering
// ── Queue interface hierarchy ─────────────────────────────────────────
//
// Iterable
// └── Collection
// └── Queue ← this interface
// ├── Deque ← double-ended queue
// │ ├── ArrayDeque ← resizable array, fast
// │ └── LinkedList ← doubly linked list
// ├── BlockingQueue ← java.util.concurrent
// │ ├── LinkedBlockingQueue
// │ ├── ArrayBlockingQueue
// │ └── PriorityBlockingQueue
// └── PriorityQueue ← heap-based priority ordering
// ── Six Queue methods — three pairs ──────────────────────────────────
//
// Operation Throws exception Returns special value
// ─────────────────────────────────────────────────────
// Insert add(e) offer(e) → false if full
// Remove remove() poll() → null if empty
// Examine element() peek() → null if empty
//
// Throws-exception form: use when the condition is a bug
// Returns-value form: use when the condition is expected/handled
Queue<String> queue = new LinkedList<>();
// ── Insertion ─────────────────────────────────────────────────────────
queue.add("Alice"); // throws IllegalStateException if full
queue.offer("Bob"); // returns false if full (safe for bounded queues)
queue.offer("Carol");
// ── Examination (peek without removal) ───────────────────────────────
String head = queue.peek(); // "Alice" — null if empty (safe)
String head2 = queue.element(); // "Alice" — throws NoSuchElementException if empty
System.out.println(head); // Alice (still in queue)
System.out.println(queue.size()); // 3 — unaffected
// ── Removal ──────────────────────────────────────────────────────────
String removed = queue.poll(); // "Alice" — null if empty (safe)
String removed2 = queue.remove(); // "Bob" — throws NoSuchElementException if empty
System.out.println(queue.size()); // 1 — only Carol remains
// ── FIFO ordering demonstrated ────────────────────────────────────────
Queue<Integer> fifo = new LinkedList<>();
fifo.offer(1);
fifo.offer(2);
fifo.offer(3);
while (!fifo.isEmpty()) {
System.out.print(fifo.poll() + " "); // 1 2 3 — first in, first out
}Queue as a Design Pattern — Producer-Consumer
// ── Producer-consumer pattern ────────────────────────────────────────
Queue<WorkItem> workQueue = new LinkedList<>();
// Producer: generates work items and adds to queue
public void produce(int count) {
for (int i = 0; i < count; i++) {
WorkItem item = new WorkItem("task-" + i);
workQueue.offer(item);
System.out.println("Produced: " + item.getId());
}
}
// Consumer: processes items from the queue
public void consume() {
WorkItem item;
while ((item = workQueue.poll()) != null) { // poll returns null when empty
process(item);
System.out.println("Consumed: " + item.getId());
}
}
// ── Breadth-first search using Queue ─────────────────────────────────
public List<Integer> bfs(Map<Integer, List<Integer>> graph,
int start) {
List<Integer> visited = new ArrayList<>();
Set<Integer> seen = new HashSet<>();
Queue<Integer> queue = new LinkedList<>();
queue.offer(start);
seen.add(start);
while (!queue.isEmpty()) {
int node = queue.poll(); // dequeue — FIFO ensures level-order
visited.add(node);
for (int neighbour : graph.getOrDefault(node, List.of())) {
if (seen.add(neighbour)) { // Set.add returns false if already present
queue.offer(neighbour); // enqueue unvisited neighbours
}
}
}
return visited;
}
// ── Thread-safe queue for concurrent producer-consumer ────────────────
// java.util.concurrent.LinkedBlockingQueue provides:
// - Thread-safe offer/poll
// - Blocking put/take (suspend until space/item available)
// - Optional capacity bound
BlockingQueue<String> safeQueue = new LinkedBlockingQueue<>(100);
// Producer thread:
new Thread(() -> {
try {
safeQueue.put("item"); // blocks if queue is full
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
// Consumer thread:
new Thread(() -> {
try {
String item = safeQueue.take(); // blocks if queue is empty
process(item);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();Choosing the Right Queue Implementation
// ── Implementation comparison ─────────────────────────────────────────
//
// Implementation Thread-safe Order offer poll Memory
// ─────────────────────────────────────────────────────────────────────
// LinkedList NO FIFO O(1) O(1) High (nodes)
// ArrayDeque NO FIFO O(1)* O(1) Low (array)
// PriorityQueue NO Priority O(log n) O(log n) Medium
// LinkedBlockingQueue YES FIFO O(1) O(1) High (nodes)
// ArrayBlockingQueue YES (bounded) FIFO O(1) O(1) Low (array)
// ConcurrentLinkedQueue YES FIFO O(1) O(1) High (nodes)
//
// * amortised
// ── WRONG: LinkedList for queue-only use ─────────────────────────────
Queue<Task> badChoice = new LinkedList<>(); // extra memory, slower
// CORRECT: ArrayDeque for single-threaded queue ────────────────────────
Queue<Task> goodChoice = new ArrayDeque<>(); // fastest, least memory
// ── Choosing by requirements ──────────────────────────────────────────
// Single-threaded, FIFO, fast:
Queue<Task> queue1 = new ArrayDeque<>();
// Single-threaded, priority order:
Queue<Task> queue2 = new PriorityQueue<>(
Comparator.comparingInt(Task::getPriority));
// Multi-threaded, unbounded, blocking:
BlockingQueue<Task> queue3 = new LinkedBlockingQueue<>();
// Multi-threaded, bounded (backpressure), blocking:
BlockingQueue<Task> queue4 = new ArrayBlockingQueue<>(1000);
// Multi-threaded, non-blocking, high throughput:
Queue<Task> queue5 = new ConcurrentLinkedQueue<>();
// ── Polymorphism — program to the interface ───────────────────────────
// Method accepts any Queue — works with all implementations
public static <T> void drainQueue(Queue<T> source,
List<T> destination) {
T item;
while ((item = source.poll()) != null) {
destination.add(item);
}
}
// Can pass any Queue implementation:
drainQueue(new ArrayDeque<>(List.of("a","b","c")), result);
drainQueue(new PriorityQueue<>(List.of(3,1,2)), numbers);
drainQueue(new LinkedBlockingQueue<>(taskList), processed);Queue Operations — Edge Cases and Iteration
// ── Null element restriction ──────────────────────────────────────────
Queue<String> queue = new ArrayDeque<>();
// queue.offer(null); // throws NullPointerException
// Null makes poll() ambiguous: null means empty? or stored value?
// Convention: never store null in a Queue
// ── Iteration without removal ─────────────────────────────────────────
Queue<String> q = new ArrayDeque<>(List.of("first", "second", "third"));
// FOR-EACH: observes without removing
for (String s : q) {
System.out.print(s + " "); // first second third
}
System.out.println(q.size()); // still 3 — nothing removed
// POLL LOOP: removes while processing
while (!q.isEmpty()) {
System.out.print(q.poll() + " "); // first second third
}
System.out.println(q.size()); // 0 — all removed
// ── Batch operations ──────────────────────────────────────────────────
Queue<Integer> from = new ArrayDeque<>(List.of(1, 2, 3, 4, 5));
Queue<Integer> to = new ArrayDeque<>();
// Transfer all: addAll preserves ordering
to.addAll(from); // from still has all elements
System.out.println(to.size()); // 5
// Drain: removes from source while adding to destination
while (!from.isEmpty()) {
to.offer(from.poll());
}
System.out.println(from.isEmpty()); // true — drained
// ── Contains check — linear scan ─────────────────────────────────────
Queue<String> names = new ArrayDeque<>(
List.of("Alice", "Bob", "Carol"));
System.out.println(names.contains("Bob")); // true — O(n) scan
System.out.println(names.contains("Dave")); // false
// For frequent membership testing, maintain a parallel Set:
Set<String> nameSet = new HashSet<>(names);
System.out.println(nameSet.contains("Bob")); // true — O(1) lookup
// ── Creating a Queue from existing collection ─────────────────────────
List<String> source = List.of("task1", "task2", "task3");
Queue<String> workQueue = new ArrayDeque<>(source); // constructor with Collection
// Initial capacity hint for ArrayDeque:
Queue<String> sized = new ArrayDeque<>(1000); // preallocate for 1000 elements