Data Structures · Stacks · A Field Guide

Evaluating postfix
expressions with a stack

This isn't a string problem. It's a memory problem — and the way you're forced to remember things points straight at one data structure.

2 3 1 * + 9 −
01 · The real problem

The operators come after their operands

You're handed an expression like 2 3 1 * + 9 -. The operators sit after the numbers they act on. That's postfix notation. Ordinary math is infix — the operator lives between its operands (2 + 3). Postfix flips it: 2 3 +.

It looks like text to chew through. It isn't. The string is just how the input is written down. The actual question is quieter and more interesting:

The question that decides everything While reading left to right, how do you remember intermediate values until an operator finally needs them?

Answer that honestly and the stack falls out on its own. You never have to memorize "postfix → stack" — you can rederive it every time.


02 · What postfix changes

Precedence is already baked in

In infix, 2 + 3 * 4 needs rules — multiply before add — so it means 2 + (3 * 4) = 14. Postfix writes the execution order out in the open:

Infix — needs rules
2 + 3 * 4
Requires precedence, parentheses, parsing.
Postfix — order is explicit
2 3 4 * +
Read left to right, do each op as you meet it.

When * appears in 2 3 4 * +, the two numbers waiting right in front of it are 3 and 4. Multiply → 12. The expression is now effectively 2 12 + → 14. No parentheses, no precedence table, no expression tree to build. You only need to remember unfinished numbers.


03 · The key observation

The newest operands get used first

Scan 2 3 1 * + 9 - slowly. You read 2, then 3, then 1 — nothing can happen, so they wait. Then * arrives. Which two numbers does it grab? The two most recent: 3 and 1. Their result, 3, becomes a fresh number that might feed a later operator.

The central rule Operands wait until an operator appears — and the most recently produced operand is consumed first.

"Most recent in, first one out." That phrase is the definition of a stack: Last In, First Out. So the stack isn't just convenient here. It matches the literal shape of the problem.


04 · Three ways to picture it

Analogies that make LIFO click

Cafeteria plates

You add clean plates on top and take from the top. The last plate stacked is the first grabbed. Numbers pile up the same way until an operator "takes" the top two.

Browser back button

Each page you visit is pushed on top of your history. "Back" always returns you to the most recent page first — the same order the stack hands operands to an operator.

Undo history

Ctrl-Z reverses your latest action, not your first. Unfinished work waits in a pile; the freshest item is resolved first. Postfix operands wait exactly like this.


05 · See it run

Step through the machine

Walk one token at a time through 2 3 1 * + 9 -. Watch numbers get pushed, and watch each operator pop the top two and push its result back. Use Next, or hit Play to autorun.

Postfix evaluator

Step 0 / 7
stack is empty
Press Next to read the first token.

Notice the last subtraction: the stack holds [5, 9], but the answer is 5 − 9 = −4, not 9 − 5. That ordering trap is the single most common bug — the next section is entirely about it.


06 · The dangerous mistake

The first pop is the right operand

For 5 9 - the operation is 5 - 9. But when you pop, the top of the stack comes off first — and the top is 9. So the first pop is the right operand, the second pop is the left one.

✗ Wrong — gives 9 − 5
int a = stack.pop();  // 9
int b = stack.pop();  // 5
int r = a - b;        // 4  ✗
✓ Right — gives 5 − 9
int right = stack.pop(); // 9
int left  = stack.pop(); // 5
int r = left - right;    // -4 ✓
The habit that saves you Name your popped values right and left — never a and b. Directional names make it almost impossible to reverse subtraction or division.

07 · The algorithm

Two cases, that's the whole thing

Once the reasoning is right, the code reads almost like English. For every token: if it's a number, push it. If it's an operator, pop two, apply, push the result. When you're done, the one value left on the stack is the answer.

# create an empty stack
for each token:
    if token is a number:
        push(token)
    else:                 # it's an operator
        right = pop()
        left  = pop()
        push( left op right )

return pop()             # the final answer

Every completed operation produces a new operand, which is why you push the result back. In 2 3 + 4 *, the 2 3 + becomes 5, and that 5 is exactly what 4 * needs next.


08 · The dry-run table

Your best debugging tool

When a stack solution misbehaves, don't stare at the code — build a table. Track the token, the action, and the stack state after it. The bug usually jumps out.

Token Action Stack after
2 push 2 [2]
3 push 3 [2, 3]
1 push 1 [2, 3, 1]
* 3 × 1 = 3 [2, 3]
+ 2 + 3 = 5 [5]
9 push 9 [5, 9]
− 5 − 9 = −4 [−4]
Sanity check on operands Inputs are non-negative, but results are not. 2 5 - is -3. Keep signed integers — plain int handles it.

09 · Why it always works

One number left, guaranteed

Each number pushes one value (+1). Each binary operator pops two and pushes one (net −1). A valid postfix expression has exactly one more operand than operators, so the stack collapses to a single value:

The counting law number of operands = number of operators + 1  →  final stack size = 1

For 2 3 1 * + 9 -: four operands (2, 3, 1, 9), three operators (*, +, −). Since 4 = 3 + 1, it's valid — and it must end with one number. The deeper reason is an invariant: at any point, the stack holds exactly the fully-evaluated sub-results that no later operator has consumed yet.


10 · The deeper connection

Postfix is a tree walked bottom-up

The expression 2 3 4 * + means 2 + (3 * 4). Draw it as a tree and read it in postorder — left child, right child, then the node — and you get the postfix string back. That's literally why it's called "post"-fix: the operator prints after its operands.

+ 2 * 3 4

postorder: 2 → 3 → 4 → * → +   =   2 3 4 * +


11 · The three notations

Where the operator sits

Same expression, 2 + (3 * 4), written three ways. The only thing that moves is the operator's position relative to its operands.

Infix
2 + (3 * 4)
Operator between. Needs precedence + parentheses.
Prefix
+ 2 * 3 4
Operator first. Naturally evaluated right → left.
Postfix
2 3 4 * +
Operator last. Evaluated left → right. Loves a stack.

12 · Cost

Linear, and you can't do better

Time
O(n)
Each token touched once: push, or two pops + one push. All constant work.
Space
O(n)
Worst case, many numbers pile up before an operator — e.g. 1 2 3 4 5 + + + +.

You have to at least look at every token, so O(n) time is optimal. A naive approach — repeatedly scanning a list for the next operator and rewriting it in place — also works but wastes effort re-searching. The stack's insight is that the operands you need are always already on top, so no searching is required.


13 · Traps to avoid

Four ways people break this

1 · Reading char by char

Tokens are space-separated and numbers can be multi-digit. Split on spaces ("12 35 +" → 12, 35), don't iterate characters.

2 · Reversed subtraction

First pop = right operand, second = left. Compute left − right, never the other way.

3 · Not pushing the result

Every operation's output is a future operand. Forget to push it and the next operator starves.

4 · Applying precedence

Postfix already encodes order. Don't reach for "* before +" — just process each operator when you meet it.


14 · The code

A clean Java solution

Prefer Deque<Integer> backed by ArrayDeque over the legacy Stack class. The switch reads naturally: a token is either one of three operators, or (default) a number.

import java.util.*;

public class Main {
    static int apply(int left, int right, String op) {
        switch (op) {
            case "+": return left + right;
            case "-": return left - right;
            case "*": return left * right;
            default: throw new IllegalArgumentException(op);
        }
    }

    static int evaluate(String expr) {
        Deque<Integer> stack = new ArrayDeque<>();
        for (String t : expr.split(" ")) {
            if (t.equals("+") || t.equals("-") || t.equals("*")) {
                int right = stack.pop();   // first pop = right
                int left  = stack.pop();   // second pop = left
                stack.push(apply(left, right, t));
            } else {
                stack.push(Integer.parseInt(t));
            }
        }
        return stack.pop();
    }

    public static void main(String[] args) {
        System.out.println(evaluate("2 3 1 * + 9 -")); // -4
    }
}

15 · One month from now

Don't memorize code — memorize this chain

  1. I scan the tokens left to right.
  2. Numbers can't be used yet, so they wait.
  3. An operator needs the two latest waiting values.
  4. "Latest first" means LIFO, and LIFO means a stack.
  5. Pop right, pop left, compute left op right, push the result.
  6. When the tokens run out, the one value left is the answer.
The transferable lesson The real skill isn't postfix — it's recognizing state. When a stream of input can't all be resolved immediately, ask: in what order will I need what I stored? If the answer is "most recent first," reach for a stack. That same instinct unlocks balanced parentheses, function calls, DFS, and monotonic-stack problems.