A queue is a linear data structure that enforces First In, First Out (FIFO) order. The earliest inserted element is removed first. Use queues when order of service matters and you must preserve arrival order.
Inputs: sequence of values arriving over time.
Constraint: preserve arrival order on output.
Primitive operations map directly to physical-world queue semantics: enqueue adds to tail, dequeue removes from head. Implementations vary by performance tradeoffs.
- Invariant 1: If
aenqueued beforeb, thenadequeued beforebunlessais removed by another operation (e.g., cancellation). - Invariant 2: After
nenqueues andmdequeues wheren>m, queue length =n - m. - Empty queue: dequeue must signal error or return sentinel value.
- Concurrent access must preserve atomicity for enqueue/dequeue.
enqueue(value)— push value to tail. O(1) amortized.dequeue()— remove and return head. O(1) amortized.peek()/front()— return head without removing. O(1).isEmpty()— boolean. O(1).size()— count. O(1) if tracked.clear()— empty queue. O(1) if pointer reset, else O(n) if releasing nodes.
| Operation | Time | Space |
|---|---|---|
| enqueue | O(1) amortized | O(1) extra |
| dequeue | O(1) amortized | O(1) extra |
| peek | O(1) | O(1) |
| memory | — | O(n) for n elements |
Notes: array implementations may require resizing. Linked-list and circular buffer avoid per-op resizing.
Traits: contiguous memory, fixed capacity or resizable, best cache behavior.
Python (resizable circular buffer)
class CircularQueue:
def __init__(self, capacity=8):
self._cap = max(2, capacity)
self._data = [None] * self._cap
self._head = 0
self._size = 0
def _resize(self, new_cap):
a = [None] * new_cap
for i in range(self._size):
a[i] = self._data[(self._head + i) % self._cap]
self._data = a
self._cap = new_cap
self._head = 0
def enqueue(self, val):
if self._size == self._cap:
self._resize(self._cap * 2)
tail = (self._head + self._size) % self._cap
self._data[tail] = val
self._size += 1
def dequeue(self):
if self._size == 0:
raise IndexError("dequeue from empty queue")
val = self._data[self._head]
self._data[self._head] = None
self._head = (self._head + 1) % self._cap
self._size -= 1
if 0 < self._size <= self._cap // 4:
self._resize(max(8, self._cap // 2))
return val
def peek(self):
if self._size == 0:
raise IndexError("peek from empty queue")
return self._data[self._head]
def is_empty(self):
return self._size == 0
def __len__(self):
return self._sizeTraits: O(1) enqueue/dequeue, no resizing, stable for large or streaming data.
Python
class Node:
__slots__ = ("val", "next")
def __init__(self, val):
self.val = val
self.next = None
class LinkedQueue:
def __init__(self):
self.head = None
self.tail = None
self._size = 0
def enqueue(self, val):
node = Node(val)
if self.tail:
self.tail.next = node
else:
self.head = node
self.tail = node
self._size += 1
def dequeue(self):
if not self.head:
raise IndexError("dequeue from empty queue")
val = self.head.val
self.head = self.head.next
if not self.head:
self.tail = None
self._size -= 1
return val
def peek(self):
if not self.head:
raise IndexError("peek from empty queue")
return self.head.val
def __len__(self):
return self._sizeUse case: implement queue using two LIFO stacks. Useful in interview settings and when only stack primitives exist.
Python
class QueueUsingStacks:
def __init__(self):
self.s_in = []
self.s_out = []
def _move_in_to_out(self):
while self.s_in:
self.s_out.append(self.s_in.pop())
def enqueue(self, val):
self.s_in.append(val)
def dequeue(self):
if not self.s_out:
self._move_in_to_out()
if not self.s_out:
raise IndexError("dequeue from empty queue")
return self.s_out.pop()
def peek(self):
if not self.s_out:
self._move_in_to_out()
if not self.s_out:
raise IndexError("peek from empty queue")
return self.s_out[-1]In multi-threaded contexts use concurrency primitives. Examples: queue.Queue in Python, ConcurrentLinkedQueue in Java, BlockingQueue variants for producer-consumer patterns. Ensure atomicity for head/tail updates. Use locks, compare-and-swap, or lock-free algorithms depending on latency and contention.
- Off-by-one in circular buffer indexing.
- Forgetting to update tail on empty->non-empty transition in linked list.
- Memory leak if references not cleared in languages with manual memory management.
- Integer overflow when using naive counters for indices in extremely long-running systems.
- Division between logical size and physical capacity. Expose size API if required.
- For two-stacks method, ensure correct amortized analysis and that peek does not mutate state.
- Breadth-first search (BFS).
- Task scheduling and job queues.
- Producer-consumer pipelines.
- Rate-limited API request queues.
- Print spooling and IO buffers.
- Message brokers and stream processing.
- Single enqueue then dequeue yields same value.
- Multiple enqueues then multiple dequeues preserve order.
- Dequeue on empty raises error.
- Peek does not remove element.
- Size reflects operations.
- Stress test with many elements to verify resizing and memory behavior.
- Concurrency tests for race conditions and lost updates.
- Fuzz token input for implementations that parse input strings.
Simple usage
q = CircularQueue()
q.enqueue(1)
q.enqueue(2)
assert q.dequeue() == 1
assert q.peek() == 2Two-stacks
q = QueueUsingStacks()
q.enqueue(1); q.enqueue(2)
assert q.dequeue() == 1
q.enqueue(3)
assert q.dequeue() == 2
assert q.dequeue() == 3[ ]-> dequeue error.[1]-> dequeue = 1.[1,2,3]-> dequeues [1,2,3].- Mixed enqueue/dequeue pairs: confirm invariants.
- Large N for performance and memory.
queue-guide/
├── README.md # this file
├── python/
│ ├── circular_queue.py
│ ├── linked_queue.py
│ ├── stacks_queue.py
│ └── tests/
│ ├── test_circular.py
│ ├── test_linked.py
│ └── test_stacks.py
├── javascript/
│ ├── circularQueue.js
│ ├── linkedQueue.js
│ └── tests/
│ └── ...
└── examples/
└── perf_benchmark.py
- Choose implementation by workload: circular buffer for high throughput, linked list for unpredictable size, blocking/concurrent queue for multi-threading.
- Expose metrics: length, capacity, head/tail indices optionally for diagnostics.
- Add instrumentation: enqueue/dequeue latency, GC pressure, memory usage.
- Add bounded-queue semantics if backpressure is required.
- Add retention policy for stale items where necessary.