DSA Notes
Complete queue implementations using three different underlying structures — circular array (O(1) all operations), singly linked list (O(1) all operations), and two stacks (O(1) amortized). Includes the classic interview question of implementing a queue using two stacks.
Why Three Implementations?
Like stacks, queues can be built on different underlying structures. The challenge with a naive array queue is that dequeue (remove from front) on a regular array is O(n) — you must shift all remaining elements left. The circular array solves this with O(1) dequeue.
Implementation 2 — Linked List Queue
Uses a singly linked list with both head (front) and tail (rear) pointers.
class _QueueNode:
__slots__ = ('data', 'next')
def __init__(self, data):
self.data = data
self.next = None
class LinkedListQueue:
"""
Queue using singly linked list.
head = front (dequeue from here)
tail = rear (enqueue here)
Both operations O(1) guaranteed.
"""
def __init__(self):
self._head = None # Front of queue
self._tail = None # Rear of queue
self._size = 0
def enqueue(self, item) -> None:
"""Add to tail (rear). O(1)."""
node = _QueueNode(item)
if self._tail is None:
self._head = self._tail = node
else:
self._tail.next = node # Current tail → new node
self._tail = node # New node is new tail
self._size += 1
def dequeue(self):
"""Remove from head (front). O(1)."""
if self._head is None:
raise IndexError("Dequeue from empty queue")
item = self._head.data
self._head = self._head.next # Advance head
if self._head is None: # Queue became empty
self._tail = None
self._size -= 1
return item
def front(self):
if self._head is None:
raise IndexError("Front of empty queue")
return self._head.data
def rear(self):
if self._tail is None:
raise IndexError("Rear of empty queue")
return self._tail.data
def is_empty(self) -> bool:
return self._head is None
def size(self) -> int:
return self._size
def __repr__(self) -> str:
if self.is_empty():
return "LinkedQueue(empty)"
elements = []
cur = self._head
while cur:
elements.append(str(cur.data))
cur = cur.next
return "LinkedQueue(front→rear): " + " → ".join(elements)Implementation 3 — Queue Using Two Stacks
Classic interview problem: Implement a queue using only two stacks.
The insight:
- Stack 1 (
inbox): Accept all new elements via enqueue - Stack 2 (
outbox): Serve dequeue requests
When outbox is empty and a dequeue is requested, pour all elements from inbox into outbox. This reverses their order — the first-enqueued element ends up on top of outbox.
Tracing the transfer:
| dequeue() | outbox is empty → transfer: |
| Pop 3 from inbox, push to outbox | inbox=[1,2], outbox=[3] |
| Pop 2 from inbox, push to outbox | inbox=[1], outbox=[3,2] |
| Pop 1 from inbox, push to outbox | inbox=[], outbox=[3,2,1] |
| Pop from outbox | return 1 ✓ (FIFO satisfied!) |
| dequeue() | outbox=[3,2], return 2 ✓ (no transfer needed) |
| dequeue() | outbox not empty → return 3 ✓ |
| dequeue() | outbox empty → transfer: |
| inbox=[4], outbox=[] | pour: inbox=[], outbox=[4] |
The Dynamic deque-Based Queue
For production Python code, the simplest and fastest queue is built on collections.deque:
Comparison of Implementations
| Feature | Circular Array | Linked List | Two Stacks | Deque |
|---|---|---|---|---|
| Enqueue | O(1) | O(1) | O(1) | O(1) |
| Dequeue | O(1) | O(1) | O(1) amortized | O(1) |
| Front/Rear | O(1) | O(1) | O(1) amortized | O(1) |
| Max capacity | Fixed | Unlimited | Unlimited | Unlimited |
| Memory per element | Low | Higher (pointer) | Medium | Low |
| Cache performance | Excellent | Poor | Good (arrays) | Good |
| Simplicity | Medium | Simple | Medium | Simplest |
| Best for | Bounded queues | Dynamic queues | Interview Q | Production |
Summary
Three approaches to implementing a queue:
- Circular Array: O(1) all operations, fixed capacity, excellent cache performance — use for bounded queues
- Linked List: O(1) all operations, unlimited capacity — use when size is unpredictable
- Two Stacks: O(1) amortized — the classic interview answer demonstrating stack-to-queue conversion
- collections.deque: Python's built-in, simplest code, best for everyday use
*Next Lesson: Circular Queue — Deep dive into circular queue internals, the wasted-space problem it solves, and applications.*
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Queue Implementation — Array, Linked List, and Two-Stack Approaches.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Data Structures & Algorithms topic.
Search Terms
data-structures-algorithms, data structures & algorithms, data, structures, algorithms, queues, queue, implementation
Related Data Structures & Algorithms Topics