Stack and Queue Data Structures Explained with Examples
Basics4 minInnovialabs editorial team
In short
A stack returns the most recently added element first (LIFO), a queue returns the oldest one first (FIFO); both are simple wrappers that restrict where you add and remove items.
The short answer
- Stack — last in, first out (LIFO). Like a pile of plates: you put a plate on top and take from the top. Operations:
push,pop,peek. - Queue — first in, first out (FIFO). Like a line at a counter: new people join at the back, the first in line is served first. Operations:
enqueue,dequeue.
Both are restrictions, not new kinds of memory. They limit access to one or two ends, which makes the code simpler and the operations O(1).
Stack vs queue vs deque
| Stack | Queue | Deque | |
|---|---|---|---|
| Order | LIFO | FIFO | Both |
| Add | top | back | front or back |
| Remove | top | front | front or back |
| Typical cost | O(1) | O(1) | O(1) |
A deque (double-ended queue) allows adding and removing at both ends. It can serve as either a stack or a queue.
Implementing a stack
A dynamic array is a perfect stack: the top is the end of the array, and appending or removing from the end is O(1) amortized.
stack = []
stack.append("a") # push
stack.append("b")
top = stack[-1] # peek -> "b"
stack.pop() # pop -> "b"
const stack = [];
stack.push("a");
stack.push("b");
stack.at(-1); // peek -> "b"
stack.pop(); // pop -> "b"
Implementing a queue
Here the naive approach hurts. list.pop(0) in Python and array.shift() in JavaScript remove from the front, which shifts all other elements — O(n) per operation.
In Python, use collections.deque:
from collections import deque
queue = deque()
queue.append("task1") # enqueue
queue.append("task2")
first = queue.popleft() # dequeue -> "task1"
JavaScript has no built-in queue. For small queues shift() is fine. For large ones, keep a read index or use a linked list:
class Queue {
constructor() { this.items = []; this.head = 0; }
enqueue(x) { this.items.push(x); }
dequeue() {
if (this.head >= this.items.length) return undefined;
const x = this.items[this.head++];
if (this.head > 1024 && this.head * 2 > this.items.length) {
this.items = this.items.slice(this.head); // drop consumed part
this.head = 0;
}
return x;
}
get size() { return this.items.length - this.head; }
}
Other options are a linked list (enqueue at the tail, dequeue at the head) or a ring buffer — a fixed-size array where the start and end indices wrap around.
Where stacks are used
- Undo history. Each action is pushed; Ctrl+Z pops the last one. Redo is usually a second stack.
- The call stack. Every function call pushes a frame with local variables and a return address;
returnpops it. Too deep recursion causes a stack overflow. - Bracket matching. Push every opening bracket; on a closing bracket, pop and check that it matches.
- Depth-first search (DFS) and backtracking, explicitly or through recursion.
Bracket matching is a classic exercise:
def balanced(s: str) -> bool:
pairs = {")": "(", "]": "[", "}": "{"}
stack = []
for ch in s:
if ch in "([{":
stack.append(ch)
elif ch in pairs:
if not stack or stack.pop() != pairs[ch]:
return False
return not stack
balanced("{[()]}") # True
balanced("([)]") # False
Where queues are used
- Task queues. Background jobs, emails, image processing: producers add tasks, workers take them in order. In production this is often a dedicated broker, but the idea is the same FIFO.
- Breadth-first search (BFS). Visit a node, add its neighbours to the queue, repeat. This finds the shortest path in an unweighted graph.
- Buffers. Keyboard input, network packets, event streams — anything processed in arrival order.
from collections import deque
def bfs(graph, start):
seen, order = {start}, []
queue = deque([start])
while queue:
node = queue.popleft()
order.append(node)
for nxt in graph[node]:
if nxt not in seen:
seen.add(nxt)
queue.append(nxt)
return order
Common mistakes
- Using
shift()orpop(0)on large queues and getting quadratic time. - Calling
popon an empty stack without a check: Python raisesIndexError, JavaScript silently returnsundefined. - Confusing order: replacing a queue with a stack in BFS turns it into a depth-first traversal.
- Deep recursion where an explicit stack would avoid hitting the call stack limit.