Queue Collection Frameworks
Why Built-In Queue Implementations?
Every major language ships a production-quality queue in its standard library. These implementations are tested, optimised, and immediately available — in interviews and production code, always reach for the built-in first.
Understanding which built-in to choose — and the exact API — separates engineers who know the language from those who guess.
Language Recommended Queue Thread-Safe Alternative ────────────────────────────────────────────────────────────────────── Java Queue<E> via ArrayDeque<E> BlockingQueue (LinkedBlockingQueue) Python collections.deque queue.Queue / queue.SimpleQueue C++ std::queue<T> (manual mutex or concurrent library) JavaScript Array (approximation) No standard built-in thread-safe queue
Java: The Queue<E> Interface
Java's queue is an interface — you choose the implementation that backs it. The Queue<E> interface defines the full contract; the implementation choice determines the internal structure and performance.
Java Queue Implementations (all implement Queue<E>):
ArrayDeque<E> — array-backed, O(1) both ends, NOT thread-safe
RECOMMENDED for most use cases
LinkedList<E> — linked-list-backed, O(1) both ends, NOT thread-safe
Use when also needing List operations
PriorityQueue<E> — heap-backed, dequeue returns smallest element
NOT FIFO — orders by priority, not arrival
LinkedBlockingQueue<E> — linked-list, BOUNDED optional, THREAD-SAFE
For producer-consumer concurrent patterns
ArrayBlockingQueue<E> — circular array, BOUNDED, THREAD-SAFE
Bounded concurrent queue with backpressure
Declaration and Initialization
1import java.util.*;
2import java.util.concurrent.*;
3
4public class JavaQueueDeclaration {
5
6 public static void main(String[] args) {
7 // RECOMMENDED: Queue interface + ArrayDeque — fastest, unordered FIFO
8 Queue<Integer> queue = new ArrayDeque<>();
9
10 // Pre-size to reduce early resizing
11 Queue<Integer> preSized = new ArrayDeque<>(100);
12
13 // Initialize from collection
14 Queue<String> fromList = new ArrayDeque<>(Arrays.asList("A", "B", "C"));
15
16 // LinkedList as Queue — use when also needing List operations
17 Queue<Integer> linked = new LinkedList<>();
18
19 // Deque interface gives access to both ends (superset of Queue)
20 Deque<Integer> deque = new ArrayDeque<>();
21
22 // Thread-safe — producer-consumer
23 BlockingQueue<Integer> blocking = new LinkedBlockingQueue<>();
24
25 // Thread-safe, bounded (max 10 elements)
26 BlockingQueue<Integer> bounded = new LinkedBlockingQueue<>(10);
27
28 // Thread-safe, bounded circular array
29 BlockingQueue<Integer> arrayBound = new ArrayBlockingQueue<>(10);
30
31 System.out.println("ArrayDeque: " + queue.size()); // 0
32 System.out.println("From list: " + fromList); // [A, B, C]
33 System.out.println("From list peek: " + fromList.peek()); // A
34 }
35}Output (Java): ArrayDeque: 0 From list: [A, B, C] From list peek: A
Java Queue API — Full Reference
1import java.util.*;
2
3public class JavaQueueAPI {
4
5 public static void main(String[] args) {
6 Queue<Integer> q = new ArrayDeque<>();
7
8 // ── ENQUEUE ──────────────────────────────────────────────
9 q.offer(10); // Add to back — returns false if full (never for ArrayDeque)
10 q.add(20); // Add to back — throws IllegalStateException if full
11 q.offer(30);
12 System.out.println("After enqueues: " + q); // [10, 20, 30]
13
14 // ── PEEK (non-destructive) ────────────────────────────────
15 System.out.println("peek(): " + q.peek()); // 10 — null if empty
16 System.out.println("element(): " + q.element()); // 10 — throws if empty
17 System.out.println("Size unchanged: " + q.size()); // 3
18
19 // ── DEQUEUE (destructive) ─────────────────────────────────
20 System.out.println("poll(): " + q.poll()); // 10 — null if empty
21 System.out.println("remove(): " + q.remove()); // 20 — throws if empty
22 System.out.println("After dequeues: " + q); // [30]
23
24 // ── QUERY ────────────────────────────────────────────────
25 System.out.println("isEmpty(): " + q.isEmpty()); // false
26 System.out.println("size(): " + q.size()); // 1
27 System.out.println("contains(30): " + q.contains(30)); // true — O(n)
28 System.out.println("contains(99): " + q.contains(99)); // false — O(n)
29
30 // ── SAFE vs THROWING — the critical distinction ──────────
31 Queue<Integer> empty = new ArrayDeque<>();
32 System.out.println("poll() on empty: " + empty.poll()); // null (safe)
33 System.out.println("peek() on empty: " + empty.peek()); // null (safe)
34 try { empty.remove(); }
35 catch (NoSuchElementException e) {
36 System.out.println("remove() on empty: throws " + e.getClass().getSimpleName());
37 }
38 try { empty.element(); }
39 catch (NoSuchElementException e) {
40 System.out.println("element() on empty: throws " + e.getClass().getSimpleName());
41 }
42
43 // ── ITERATION ────────────────────────────────────────────
44 Queue<Integer> iter = new ArrayDeque<>(Arrays.asList(1, 2, 3, 4, 5));
45 System.out.print("Iteration order: ");
46 for (int v : iter) System.out.print(v + " "); // 1 2 3 4 5
47 System.out.println();
48 System.out.println("Size unchanged after iteration: " + iter.size()); // 5
49 // Note: for-each does NOT dequeue — it iterates without removing
50
51 // ── DRAIN (dequeue all) ───────────────────────────────────
52 System.out.print("Drain: ");
53 while (!iter.isEmpty()) System.out.print(iter.poll() + " ");
54 System.out.println();
55 System.out.println("After drain isEmpty: " + iter.isEmpty()); // true
56
57 // ── CLEAR ────────────────────────────────────────────────
58 Queue<Integer> toClear = new ArrayDeque<>(Arrays.asList(1, 2, 3));
59 toClear.clear();
60 System.out.println("After clear isEmpty: " + toClear.isEmpty()); // true
61 }
62}Output (Java): After enqueues: [10, 20, 30] peek(): 10 element(): 10 poll(): 10 remove(): 20 After dequeues: [30] isEmpty(): false poll() on empty: null peek() on empty: null remove() on empty: throws NoSuchElementException Drain: 1 2 3 4 5
Java: ArrayDeque vs LinkedList as Queue
Both implement the Queue<E> interface with identical method signatures. The choice is a performance and memory trade-off.
ArrayDeque<E>:
Internal storage: resizable circular array
Memory per element: object reference only (~8 bytes)
Cache performance: excellent — contiguous array, prefetch-friendly
Resizing: amortized O(1) per enqueue — doubles when full
Null elements: NOT allowed — throws NullPointerException
Use when: general FIFO queue, most algorithm problems
LinkedList<E>:
Internal storage: doubly linked list
Memory per element: ~48 bytes (prev ptr, next ptr, data ptr, object header)
Cache performance: poor — nodes scattered across heap
Resizing: never needed — each node allocated individually
Null elements: allowed
Use when: also need mid-queue insertion/deletion with iterators,
or need both Queue and List operations simultaneously
Performance comparison (n = 10 million operations):
ArrayDeque enqueue+dequeue: ~0.8s
LinkedList enqueue+dequeue: ~2.5s
ArrayDeque is ~3x faster in practice.
1import java.util.*;
2
3public class ArrayDequeVsLinkedList {
4
5 public static void main(String[] args) {
6 // Both share the same Queue interface
7 Queue<String> arrayQ = new ArrayDeque<>();
8 Queue<String> linkedQ = new LinkedList<>();
9
10 for (Queue<String> q : Arrays.asList(arrayQ, linkedQ)) {
11 q.offer("X"); q.offer("Y"); q.offer("Z");
12 System.out.println(q.getClass().getSimpleName()
13 + ": peek=" + q.peek()
14 + ", poll=" + q.poll()
15 + ", size=" + q.size());
16 }
17
18 // ArrayDeque does NOT allow null
19 try {
20 arrayQ.offer(null);
21 } catch (NullPointerException e) {
22 System.out.println("ArrayDeque: null not allowed");
23 }
24
25 // LinkedList DOES allow null
26 linkedQ.offer(null);
27 System.out.println("LinkedList peek after null offer: " + linkedQ.peek()); // Y
28 System.out.println("LinkedList size with null: " + linkedQ.size()); // 3
29
30 // LinkedList as Queue AND List
31 LinkedList<Integer> both = new LinkedList<>();
32 both.offer(1); both.offer(2); both.offer(3);
33 both.add(1, 99); // List operation: insert at index 1
34 System.out.println("LinkedList as both: " + both); // [1, 99, 2, 3]
35 }
36}Python: queue.Queue — Thread-Safe Queue
collections.deque is not thread-safe for concurrent producers and consumers. Python's queue module provides thread-safe options.
1// Java equivalent: BlockingQueue
2import java.util.concurrent.*;
3
4public class ThreadSafeQueue {
5
6 public static void main(String[] args) throws InterruptedException {
7 // LinkedBlockingQueue — unbounded, thread-safe
8 BlockingQueue<Integer> q = new LinkedBlockingQueue<>();
9
10 // Non-blocking (like poll/offer)
11 q.offer(10); // Returns false if full
12 Integer val = q.poll(); // Returns null if empty
13 System.out.println("Non-blocking poll: " + val); // 10
14
15 // Blocking — waits until space available / element arrives
16 q.put(20); // Blocks if queue is full
17 Integer bval = q.take(); // Blocks if queue is empty
18 System.out.println("Blocking take: " + bval); // 20
19
20 // Timed blocking
21 q.offer(30, 1, TimeUnit.SECONDS); // Wait up to 1s
22 Integer tval = q.poll(1, TimeUnit.SECONDS); // Wait up to 1s
23 System.out.println("Timed poll: " + tval); // 30
24
25 // Bounded queue — at most 5 elements
26 BlockingQueue<Integer> bounded = new ArrayBlockingQueue<>(5);
27 for (int i = 1; i <= 5; i++) bounded.offer(i);
28 System.out.println("Bounded full: " + (bounded.remainingCapacity() == 0)); // true
29 }
30}Output (Python thread-safe): Non-blocking get: 10 Blocking get: 20 Timed get: 30 task_done + join: all processed Consumed: 0 Consumed: 10 ... All produced items consumed.
C++: std::queue vs std::deque
std::queue is a container adapter that restricts access. std::deque is the full underlying container with additional capabilities.
std::queue<T>:
Wraps a container (deque by default) and exposes ONLY:
push(val) — enqueue to back
pop() — dequeue from front (RETURNS VOID)
front() — peek front
back() — peek back
empty() — isEmpty check
size() — element count
No random access, no iterators, no clear()
std::deque<T> (underlying container):
Full double-ended container:
push_back / push_front — O(1) both ends
pop_back / pop_front — O(1) both ends
operator[] — O(1) random access
begin/end — full iterator support
clear() — O(n) erase all
insert / erase — O(n) mid-sequence
When to prefer std::deque over std::queue:
Need iterators or range-based for loops
Need clear()
Need random access by index
Building a sliding window algorithm
Need to inspect non-front elements
1// Java equivalent: Queue (restricted) vs Deque (full double-ended)
2import java.util.*;
3
4public class QueueVsDeque {
5
6 public static void main(String[] args) {
7 // Queue — FIFO restricted interface
8 Queue<Integer> queue = new ArrayDeque<>();
9 queue.offer(1); queue.offer(2); queue.offer(3);
10 System.out.println("Queue: " + queue);
11 // queue.getLast() — NOT available (would need Deque)
12 // queue.descendingIterator() — NOT available
13
14 // Deque — full double-ended interface (superset of Queue)
15 Deque<Integer> deque = new ArrayDeque<>();
16 deque.offer(1); deque.offer(2); deque.offer(3);
17 System.out.println("Deque first: " + deque.peekFirst()); // 1
18 System.out.println("Deque last: " + deque.peekLast()); // 3
19 System.out.print("Reversed: ");
20 deque.descendingIterator().forEachRemaining(v -> System.out.print(v + " "));
21 System.out.println(); // 3 2 1
22
23 // Deque as Stack (push/pop to front)
24 deque.push(0); // addFirst
25 System.out.println("After push(0): " + deque); // [0, 1, 2, 3]
26 System.out.println("Pop: " + deque.pop()); // 0 — LIFO
27 }
28}Complete API Comparison Table
| Operation | Java ArrayDeque | Python deque | C++ std::queue | C++ std::deque | JS Queue (Map) |
|---|---|---|---|---|---|
| Enqueue back | offer(e) O(1) | append(e) O(1) | push(e) O(1) | push_back(e) O(1) | enqueue(e) O(1) |
| Dequeue front | poll() O(1) | popleft() O(1) | front()+pop() | pop_front() O(1) | dequeue() O(1) |
| Peek front | peek() O(1) | [0] O(1) | front() O(1) | front() O(1) | peek() O(1) |
| Peek back | peekLast() O(1) | [-1] O(1) | back() O(1) | back() O(1) | — |
| Add to front | addFirst(e) O(1) | appendleft(e) O(1) | ❌ | push_front(e) O(1) | — |
| Remove back | pollLast() O(1) | pop() O(1) | ❌ | pop_back() O(1) | — |
| isEmpty | isEmpty() O(1) | len(dq)==0 O(1) | empty() O(1) | empty() O(1) | isEmpty() O(1) |
| Size | size() O(1) | len(dq) O(1) | size() O(1) | size() O(1) | size() O(1) |
| Random access | ❌ | dq[i] O(n) | ❌ | dq[i] O(1) | ❌ |
| Iterators | ✅ | ✅ | ❌ | ✅ | ✅ (custom) |
| Clear | clear() O(n) | clear() O(n) | ❌ (loop pop) | clear() O(n) | clear() O(1) |
| Null allowed | ❌ throws | ✅ | ✅ | ✅ | ✅ |
| Thread-safe | ❌ | ❌ | ❌ | ❌ | ❌ |
| Pop returns | value | value | void (read first) | value | value |
When to Use Each
Java: Queue<E> via ArrayDeque<E> → standard FIFO, algorithm problems Queue<E> via LinkedList<E> → only if also needing List operations Deque<E> via ArrayDeque<E> → sliding window, monotonic deque BlockingQueue → producer-consumer, concurrent patterns Python: collections.deque → all algorithm problems, BFS queue.Queue → multi-threaded producer-consumer queue.SimpleQueue → multi-threaded, no task_done needed list (pop(0)) → NEVER for queues — O(n) dequeue C++: std::queue<T> → pure FIFO, simplest API std::queue<T, list<T>> → guaranteed O(1), no amortized std::deque<T> → need iterators, clear(), random access Custom ThreadSafeQueue → concurrent producer-consumer JavaScript: Map-based Queue → O(1) both ends, algorithm problems Array + shift() → only for tiny n where O(n) is acceptable Array + two-pointer → simulate queue with read/write index
Common Mistakes Across All Languages
Java: Using remove() instead of poll() in BFS. remove() throws NoSuchElementException when the queue is empty. In BFS, the queue naturally empties — this causes a crash at the algorithm's end if remove() is used. Always use poll() (returns null) in BFS and algorithm code.
Python: Using list.pop(0) as dequeue. pop(0) is O(n) — it shifts every remaining element. In BFS over n nodes, this is O(n²) total. Always use collections.deque.popleft() which is O(1).
C++: Calling front() or pop() on an empty std::queue. Both are undefined behavior — no exception, the program may crash or silently corrupt memory. Always check empty() first. Unlike Java and Python, C++ has no safe null-returning alternative.
C++: Expecting queue::pop() to return the removed value. It returns void. Read front() then call pop() — two separate steps. Many developers expect pop() to return the element (like Java/Python) and introduce a silent bug.
Python: Using queue.Queue when collections.deque is intended. queue.Queue has blocking behaviour and task tracking (task_done/join) — unnecessary overhead for single-threaded algorithm work. Use collections.deque for algorithmic problems; queue.Queue only for multithreaded scenarios.
JavaScript: Not accounting for Array.shift() O(n) complexity. It looks like a simple dequeue but is O(n). For interview problems with n > 1000 or performance benchmarks, use the Map-based Queue or a similar O(1) implementation.
Interview Questions
Q: What is the difference between offer() and add() in Java's Queue interface?
Both enqueue an element to the back. add() throws IllegalStateException if the queue is at capacity — relevant for bounded queues. offer() returns false instead of throwing. For ArrayDeque (unbounded), both always succeed and behave identically. In algorithm code, offer/poll/peek are always preferred over add/remove/element — the former trio never throws when empty or full, making BFS and similar algorithms cleaner and safer.
Q: Why is collections.deque preferred over list for queue operations in Python?
deque.popleft() is O(1) — it updates an internal block pointer without moving any elements. list.pop(0) is O(n) — it shifts every remaining element one position left to fill the gap. For a BFS traversal over n nodes, list.pop(0) gives O(n²) total while deque.popleft() gives O(n). The API is equally simple; the performance difference is dramatic at scale.
Q: In C++, what is the difference between std::queue and std::deque?
std::queue is a container adapter — it wraps std::deque (by default) and exposes only FIFO operations: push, pop (void), front, back, empty, size. No iterators, no clear(), no random access. std::deque is the full container that std::queue wraps — it has push_back/push_front, pop_back/pop_front, operator[], full iterators, and clear(). Use std::queue when you want the interface to enforce FIFO discipline. Use std::deque when you need the full double-ended capability or iterators.
Quick Quiz
Question 1: In Java, queue.poll() vs queue.remove() on an empty ArrayDeque:
- ›A) Both return
null - ›B) Both throw
NoSuchElementException - ›C)
poll()returnsnull;remove()throwsNoSuchElementException - ›D)
poll()throws;remove()returnsnull
Answer: C) poll() returns null; remove() throws. This is the safe vs throwing API distinction. In BFS and other algorithm code, always use poll() to avoid crashes when the queue empties normally.
Question 2: Python list.pop(0) in a BFS over n nodes gives what total complexity?
- ›A) O(n) — same as
deque.popleft() - ›B) O(n log n) — binary search involved
- ›C) O(n²) — each
pop(0)is O(n), called n times - ›D) O(1) — Python optimises index-0 removal
Answer: C) O(n²). list.pop(0) shifts all remaining elements left — O(n). Called n times in BFS: O(n) × n = O(n²). Use collections.deque.popleft() for O(1) dequeue and O(n) total BFS.
Question 3: In C++, std::queue<int> q; q.push(5); int val = q.pop(); — what is wrong?
- ›A)
pushshould beenqueue - ›B)
q.pop()returnsvoid— cannot assign toval - ›C)
std::queuedoes not exist - ›D)
int valshould beauto val
Answer: B) q.pop() returns void. In C++, std::queue::pop() removes the front element and returns nothing. The correct pattern is: int val = q.front(); q.pop(); — read then remove, two separate calls.
Question 4: Which Python option should you use for a multi-threaded producer-consumer pattern?
- ›A)
collections.deque— fastest O(1) operations - ›B)
list— simplest API - ›C)
queue.Queue— thread-safe with blocking and task tracking - ›D)
queue.SimpleQueue— fastest thread-safe option
Answer: C) queue.Queue. collections.deque operations are atomic for single appends/pops but the deque itself is not thread-safe for combined check-then-act patterns. queue.Queue provides thread-safe put/get with optional blocking, timeout, and task_done/join for producer-consumer synchronisation. queue.SimpleQueue is lighter-weight but lacks maxsize and task_done.
Summary
Every major language provides a production-quality queue through its standard library. The choice within each language matters for correctness, performance, and thread safety.
Key decisions to carry forward:
- ›Java — always use
Queue<E>withArrayDeque<E>viaoffer/poll/peek; useLinkedListonly if also needing List operations; useBlockingQueuefor concurrent patterns - ›Python — use
collections.dequewithappend/popleft/[0]for all algorithm problems; never uselist.pop(0)(O(n)); usequeue.Queuefor multithreaded producer-consumer - ›C++ —
std::queue<T>for pure FIFO;std::deque<T>when iterators, clear(), or random access are needed;pop()returns void — always readfront()first - ›JavaScript — no built-in O(1) queue; use a Map-based queue for correct O(1);
Array.shift()is O(n) — acceptable only for small n in simple interview problems - ›
contains()/x in qis O(n) on all implementations — queues are not designed for membership testing - ›Thread-safe queues: Java
BlockingQueue, Pythonqueue.Queue—collections.dequeandArrayDequeare NOT thread-safe - ›C++
std::queue::pop()is the most common cross-language mistake — returns void, not the value
In the next topic, you will explore the Circular Queue — the fixed-size ring buffer that underlies all circular array queue implementations, with full modulo arithmetic and its real-world applications.