9 min read

LIFO vs FIFO: Stacks, Queues, and Why the Order You Take Things Out Matters

The two simplest rules for ordering work — last-in-first-out and first-in-first-out — explained with plates, bakery lines, real code, and the places each one quietly runs your software (the call stack, undo, message queues, BFS, even inventory accounting).

Data StructuresAlgorithmsFundamentals
LIFO vs FIFO: Stacks, Queues, and Why the Order You Take Things Out Matters

Two piles, two rules

You are washing dishes. Clean plates go on a pile. When someone needs a plate, they take the top one — the one you put there most recently. The plate at the bottom might sit for a week. That pile is LIFO: last in, first out.

Now you are in line at a bakery. The person who walked in first gets served first. Nobody skips. That line is FIFO: first in, first out.

Same items, same operations (add one, remove one). The only difference is which one comes out. That single decision is the entire difference between a stack and a queue, and it shows up in far more of your code than you probably notice.

LIFO (stack)                  FIFO (queue)

push 1  ->  [1]               enqueue 1  ->  [1]
push 2  ->  [1, 2]            enqueue 2  ->  [1, 2]
push 3  ->  [1, 2, 3]         enqueue 3  ->  [1, 2, 3]
pop     ->  3   [1, 2]        dequeue    ->  1   [2, 3]
pop     ->  2   [1]           dequeue    ->  2   [3]

Both receive 1, 2, 3. The stack hands them back as 3, 2, 1. The queue hands them back as 1, 2, 3.

LIFO: the stack

A stack has one open end. You add there, you remove there. The vocabulary:

Operation Meaning Cost
push put an item on top O(1)
pop remove and return the top item O(1)
peek look at the top item without removing O(1)

In JavaScript an array already is a stack if you only touch its end:

const stack = []

stack.push('a')
stack.push('b')
stack.push('c')

stack.pop()             // 'c'
stack[stack.length - 1] // 'b'  (peek)

In Python, same story with a list:

stack = []
stack.append("a")
stack.append("b")
stack.append("c")

stack.pop()   # 'c'
stack[-1]     # 'b'  (peek)

push and pop on the end of an array are constant time because nothing else has to move. That is why every language gives you a stack for free.

Where LIFO is already running your code

The call stack. When a() calls b() which calls c(), the runtime pushes a frame for each. c must finish before b resumes, and b before a. Last called, first finished. When you see this:

RangeError: Maximum call stack size exceeded

that is a stack that grew until it ran out of room — usually a recursive function with no base case.

Undo. Every edit is pushed. Ctrl+Z pops the most recent one. You never want undo to revert the oldest change — that would be FIFO and it would be useless.

Back button. Browser history is a stack of pages. Back pops the top.

Matching brackets. The classic interview question and a real job in every parser, linter, and editor:

function balanced(src) {
  const pairs = { ')': '(', ']': '[', '}': '{' }
  const stack = []

  for (const ch of src) {
    if ('([{'.includes(ch)) stack.push(ch)
    else if (ch in pairs) {
      if (stack.pop() !== pairs[ch]) return false
    }
  }
  return stack.length === 0
}

balanced('{[()]}')  // true
balanced('{[(])}')  // false

An opening bracket is pushed; a closing bracket must match the most recent unclosed opener. That is LIFO by definition.

Depth-first search. Explore one branch as far as it goes before backing up. Recursion does this with the call stack; the iterative version uses an explicit one.

FIFO: the queue

A queue has two ends. Items enter at the back and leave from the front.

Operation Meaning Cost
enqueue add an item to the back O(1)
dequeue remove and return the front item O(1)
peek look at the front without removing O(1)

Here is the trap. A JavaScript array looks like a queue:

const queue = []
queue.push('a')
queue.push('b')
queue.shift()   // 'a'

But shift() removes the first element and then moves every remaining element one slot left. That is O(n) per dequeue. With a hundred items nobody notices. With a hundred thousand items in a hot loop, your "queue" is quietly quadratic.

Python names this problem explicitly and hands you the fix:

from collections import deque

queue = deque()
queue.append("a")
queue.append("b")
queue.popleft()   # 'a'   -- O(1), no shifting

deque is a double-ended queue. Both ends are O(1). If you find yourself writing list.pop(0) in Python or array.shift() in a loop in JavaScript, reach for a deque (or a linked list, or a ring buffer) instead.

A minimal O(1) queue in JavaScript, using two indices instead of shifting:

class Queue {
  #items = {}
  #head = 0
  #tail = 0

  enqueue(x) { this.#items[this.#tail++] = x }

  dequeue() {
    if (this.#head === this.#tail) return undefined
    const x = this.#items[this.#head]
    delete this.#items[this.#head++]
    return x
  }

  get size() { return this.#tail - this.#head }
}

Where FIFO is already running your code

The event loop. Every click handler, every resolved fetch, every setTimeout callback lands in a task queue and runs in arrival order. If it didn't, a click from two seconds ago could fire after one from just now.

Message queues and job workers. RabbitMQ, SQS, Redis lists, Laravel queues, BullMQ — the whole category exists to hold work in the order it was submitted and hand it to workers one at a time. An email requested first goes out first.

Breadth-first search. Visit everything one step away, then everything two steps away, and so on. You push neighbours to the back and pull from the front. Shortest path in an unweighted graph is a BFS, and BFS is a queue.

from collections import deque

def shortest_path_len(graph, start, goal):
    queue = deque([(start, 0)])
    seen = {start}
    while queue:
        node, dist = queue.popleft()
        if node == goal:
            return dist
        for nxt in graph[node]:
            if nxt not in seen:
                seen.add(nxt)
                queue.append((nxt, dist + 1))
    return None

Swap popleft() for pop() and this becomes DFS — and stops returning the shortest path. The data structure is the algorithm here.

Rate limiting, buffering, streaming. Network buffers, keyboard input, print spoolers, video frames: anything where order of arrival must equal order of processing.

Choosing

Ask one question: when I take something out, do I want the newest or the oldest?

You want... Use
To reverse something stack
To backtrack / undo / return to where you came from stack
To match nested things (brackets, tags, scopes) stack
To process in the order things arrived (fairness) queue
To spread work across workers queue
Shortest path / level-by-level traversal queue

Two more rules of thumb:

  • Recursion is a hidden stack. If a recursive solution blows the call stack, rewrite it with an explicit stack and a loop. Same logic, heap memory instead of stack memory.
  • A queue you drain immediately is not a queue. If you enqueue and dequeue in the same function with nothing in between, you wanted a variable.

The same words in accounting

If you search "LIFO vs FIFO" you will also hit inventory accounting, and it is the same idea applied to money. A shop buys 10 units at $5 in January and 10 units at $7 in March, then sells 10 units in April. What did those units cost?

  • FIFO says the January ones sold first: cost of goods sold is $50, the $7 units remain in inventory.
  • LIFO says the March ones sold first: cost of goods sold is $70, the $5 units remain.

When prices rise, LIFO reports higher costs and lower profit (and lower tax), which is why it is still used in the US and banned under IFRS in most of the rest of the world. Different domain, identical rule: which one leaves first.

Takeaways

  • LIFO = stack. One end. Newest out first. Free in every language via push/pop on an array.
  • FIFO = queue. Two ends. Oldest out first. Not free — shift() and pop(0) are O(n). Use deque, a linked list, or an index-based queue.
  • The choice is rarely cosmetic. Swapping one for the other turns DFS into BFS, undo into nonsense, and fair job processing into starvation.
  • If you cannot name which of the two a piece of code needs, you do not yet understand its ordering requirements. Figure that out first.