Stacks and Queues: LIFO vs FIFO
Stacks and queues are two of the most fundamental data structures in computer science, and they show up constantly in coding interviews. They are both simple. They both store a collection of elements. But the order in which they let you access those elements is completely different, and that difference is what makes each one useful for different kinds of problems.
If you have ever wondered about stack vs queue, when to reach for one over the other, or how to spot them in interview problems, this post covers it all.
LIFO vs FIFO: the core difference
The entire distinction between stacks and queues comes down to two acronyms:
- Stack = LIFO (Last In, First Out). The most recently added element is the first one removed. Think of a stack of plates: you put new plates on top and take from the top.
- Queue = FIFO (First In, First Out). The earliest added element is the first one removed. Think of a line at a coffee shop: the first person in line gets served first.
That is it. Everything else flows from this single difference.
Watching them side by side
The best way to internalize LIFO FIFO is to see the same sequence of operations on both structures at the same time. Let's add A, B, and C to each, then remove everything and compare what comes out.
Step 1: Push A onto stack. Enqueue A into queue.
Both structures now contain one element: A.
Step 2: Push B. Enqueue B.
Stack: B is on top. Queue: B goes to the back.
Step 3: Push C. Enqueue C.
Three items in each. Now let's remove one and see the difference.
Step 4: Pop from stack. Dequeue from queue.
Stack popped C (last in). Queue dequeued A (first in). This is the core difference.
Step 5: Pop again. Dequeue again.
Stack popped B. Queue dequeued B. The removal order is reversed.
Step 6: Pop the last item. Dequeue the last item.
Stack popped A (first in, last out). Queue dequeued C (last in, last out). Both empty.
Notice the punchline: we inserted A, B, C into both structures in the same order. The stack gave them back as C, B, A (reversed). The queue gave them back as A, B, C (original order). That reversal property is exactly why stacks are useful for problems involving undo, backtracking, and nesting.
Stack operations and complexity
A stack supports two main operations:
| Operation | What it does | Time |
|---|---|---|
push(x) | Add x to the top | O(1) |
pop() | Remove and return the top element | O(1) |
peek() / top() | Look at the top element without removing it | O(1) |
isEmpty() | Check if the stack is empty | O(1) |
Everything is O(1). That is what makes stacks so efficient. You never need to search, shift, or resize (assuming a reasonable implementation).
Array-based stack (Python)
In Python, a list works perfectly as a stack. append() pushes, pop() removes from the end. Both are amortized O(1).
stack = []
stack.append(10) # push 10
stack.append(20) # push 20
stack.append(30) # push 30
top = stack[-1] # peek: 30
val = stack.pop() # pop: 30
# stack is now [10, 20]
In Python, always use append() and pop() (no argument) for stack operations. Never use insert(0, x) or pop(0) on a list. Those are O(n) because they shift every element.
Linked-list-based stack
You can also build a stack with a linked list by always inserting and removing at the head. Each operation is O(1) with no amortization needed.
class Node:
def __init__(self, val, next_node=None):
self.val = val
self.next = next_node
class LinkedStack:
def __init__(self):
self.head = None
def push(self, val):
self.head = Node(val, self.head)
def pop(self):
if not self.head:
raise IndexError("pop from empty stack")
val = self.head.val
self.head = self.head.next
return val
def peek(self):
if not self.head:
raise IndexError("peek at empty stack")
return self.head.val
When would you use a linked-list stack over an array stack? In practice, almost never in Python. The built-in list is fast enough. But in languages where dynamic arrays are expensive or when you need guaranteed O(1) per operation (no amortized resizing), a linked-list stack can be the better choice.
Queue operations and complexity
A queue also supports O(1) operations, but from two different ends:
| Operation | What it does | Time |
|---|---|---|
enqueue(x) | Add x to the back | O(1) |
dequeue() | Remove and return the front element | O(1) |
peek() / front() | Look at the front element without removing it | O(1) |
isEmpty() | Check if the queue is empty | O(1) |
Array-based queue (Python)
Here is the catch: a plain Python list is a bad queue. list.pop(0) is O(n) because it shifts every remaining element. Instead, use collections.deque, which is a double-ended queue with O(1) operations on both ends.
from collections import deque
q = deque()
q.append(10) # enqueue 10
q.append(20) # enqueue 20
q.append(30) # enqueue 30
front = q[0] # peek: 10
val = q.popleft() # dequeue: 10
# q is now deque([20, 30])
A common interview mistake: using list.pop(0) for a queue. Interviewers notice this because it turns an O(n) algorithm into O(n^2). Always use deque for queues in Python.
Linked-list-based queue
With a linked list, you keep a pointer to both the head (front) and tail (back). Enqueue at the tail, dequeue from the head. Both O(1).
class Node:
def __init__(self, val, next_node=None):
self.val = val
self.next = next_node
class LinkedQueue:
def __init__(self):
self.head = None
self.tail = None
def enqueue(self, val):
node = Node(val)
if self.tail:
self.tail.next = node
self.tail = node
if not self.head:
self.head = node
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
return val
When to use a stack vs a queue
This is the practical question that matters for interviews. Here is a quick decision guide:
Reach for a stack when:
- You need to match or validate nested structures (parentheses, HTML tags, expressions)
- You need to process elements in reverse order
- You need to undo or backtrack to a previous state
- The problem asks about the next/previous greater or smaller element (monotonic stack)
- You are converting recursion to an iterative approach
Reach for a queue when:
- You need to process elements in the order they arrived (BFS, task scheduling)
- The problem involves levels or layers (level-order tree traversal, shortest path in unweighted graph)
- You are implementing breadth-first search on a graph or grid
- The problem involves topological ordering of dependencies
Here is a handy mental model: if the problem feels like "go deep first, then come back" (DFS), use a stack. If it feels like "process everything at this distance before going further" (BFS), use a queue.
Stack interview problems
Stacks show up in a huge range of interview problems. Here are some of the most important ones to master:
Matching and validation
- Valid Parentheses - The classic stack problem. Push openers, pop when you see a closer, verify the match. This is the first stack problem you should know cold.
Monotonic stack
- Daily Temperatures - For each day, find how many days until a warmer temperature. The monotonic stack pattern processes elements in one pass by maintaining a stack in sorted order.
Design problems
- Min Stack - Design a stack that supports
getMin()in O(1). The trick is maintaining an auxiliary stack that tracks the current minimum at each level.
Going deeper
For a comprehensive breakdown of every stack pattern (bracket matching, monotonic stacks, state save/restore), check out the Stack Patterns guide. It covers the techniques that apply across all stack problems.
Queue interview problems
Queues are the backbone of breadth-first search, which makes them essential for graph and grid problems:
BFS on grids
- Number of Islands - Count connected components in a 2D grid. BFS with a queue explores each island level by level, marking visited cells as you go. (This problem can also be solved with DFS, but the BFS approach is a great way to practice queues.)
Topological sort
- Course Schedule - Given prerequisite pairs, determine if you can finish all courses. This is cycle detection via topological sort, and the BFS version (Kahn's algorithm) uses a queue to process nodes with zero in-degree.
Stacks and queues compared
Here is a side-by-side summary of everything we have covered:
| Stack | Queue | |
|---|---|---|
| Order | LIFO (Last In, First Out) | FIFO (First In, First Out) |
| Add | push (to top) | enqueue (to back) |
| Remove | pop (from top) | dequeue (from front) |
| Python | list with append()/pop() | collections.deque with append()/popleft() |
| Use cases | Matching, backtracking, DFS | BFS, scheduling, level-order |
| Key pattern | "Most recent first" | "Oldest first" |
Building fluency with stacks and queues
Understanding stack vs queue conceptually is the easy part. The hard part is writing the code quickly and correctly under interview pressure. You need to be able to implement a monotonic stack without thinking, reach for deque automatically for BFS, and recognize which data structure a problem needs within the first minute of reading it.
That is where deliberate, spaced practice makes the difference. Instead of reading about LIFO FIFO once and hoping it sticks, you practice the core operations and stack queue interview problems at increasing intervals. Each time you drill, you reinforce the pattern a little more until it becomes automatic.
CodeBricks breaks these problems into their reusable building blocks and schedules them with spaced repetition. You type the code from scratch each session, and the spacing adapts to how well you actually remember each piece.
Related posts
- Stack Patterns - Deep dive into bracket matching, monotonic stacks, and state save/restore
- Valid Parentheses - The classic first stack problem, explained step by step
- Daily Temperatures - The best introduction to monotonic stacks
- Number of Islands - BFS grid traversal with a queue
- Course Schedule - Topological sort with Kahn's algorithm (queue-based BFS)