DSA Tutorial
🔍

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

Java
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

Java
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.
Java
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.

Java
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
Java
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

OperationJava ArrayDequePython dequeC++ std::queueC++ std::dequeJS Queue (Map)
Enqueue backoffer(e) O(1)append(e) O(1)push(e) O(1)push_back(e) O(1)enqueue(e) O(1)
Dequeue frontpoll() O(1)popleft() O(1)front()+pop()pop_front() O(1)dequeue() O(1)
Peek frontpeek() O(1)[0] O(1)front() O(1)front() O(1)peek() O(1)
Peek backpeekLast() O(1)[-1] O(1)back() O(1)back() O(1)
Add to frontaddFirst(e) O(1)appendleft(e) O(1)push_front(e) O(1)
Remove backpollLast() O(1)pop() O(1)pop_back() O(1)
isEmptyisEmpty() O(1)len(dq)==0 O(1)empty() O(1)empty() O(1)isEmpty() O(1)
Sizesize() O(1)len(dq) O(1)size() O(1)size() O(1)size() O(1)
Random accessdq[i] O(n)dq[i] O(1)
Iterators✅ (custom)
Clearclear() O(n)clear() O(n)❌ (loop pop)clear() O(n)clear() O(1)
Null allowed❌ throws
Thread-safe
Pop returnsvaluevaluevoid (read first)valuevalue

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() returns null; remove() throws NoSuchElementException
  • D) poll() throws; remove() returns null

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) push should be enqueue
  • B) q.pop() returns void — cannot assign to val
  • C) std::queue does not exist
  • D) int val should be auto 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> with ArrayDeque<E> via offer/poll/peek; use LinkedList only if also needing List operations; use BlockingQueue for concurrent patterns
  • Python — use collections.deque with append/popleft/[0] for all algorithm problems; never use list.pop(0) (O(n)); use queue.Queue for 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 read front() 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 q is O(n) on all implementations — queues are not designed for membership testing
  • Thread-safe queues: Java BlockingQueue, Python queue.Queuecollections.deque and ArrayDeque are 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.

Queue Collection Frameworks