Skip to content
Innovialabs

Stack and Queue Data Structures Explained with Examples

Basics4 min

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

StackQueueDeque
OrderLIFOFIFOBoth
Addtopbackfront or back
Removetopfrontfront or back
Typical costO(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; return pops 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() or pop(0) on large queues and getting quadratic time.
  • Calling pop on an empty stack without a check: Python raises IndexError, JavaScript silently returns undefined.
  • 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.

FAQ

A stack gives back the newest element first, a queue gives back the oldest element first.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.