skip to content

In a stack-based postfix evaluator, why must the first value popped be the right operand?

level: middleimportance: should knowfreq 52%

answer

  1. Which operand reached the stack most recently
  2. Reading order is left then right
  3. Try it on subtraction, not addition
  4. Commutative operators hide the mistake
  5. Second pop is the left-hand operand

basics

~20 s

Operands are pushed left to right, so the top of the stack is the right-hand operand and the value beneath it is the left. Reversing the two pops silently inverts subtraction and division while sums and products still look correct.

solid answer

~50 s

In postfix, `a b -` means a minus b, and the evaluator pushed `a` first, then `b`. Because the stack returns the most recent push, the first pop is `b` — the **right** operand — and the second pop is `a`. So the operator must be applied as `apply(op, second_pop, first_pop)`. Get it backwards and commutative operators hide the bug completely: addition and multiplication give the same answer either way, so a formula engine's test suite full of sums stays green while every subtraction and division comes out inverted. In a spreadsheet computing `(gross - fee) * rate`, that turns 67.05 into -67.05 and a division into its reciprocal. The other two invariants worth stating: a binary operator with fewer than two values on the stack means a malformed expression, and a well-formed expression ends with exactly one value left.

code

pseudocode · 13 lines
pseudocode
S = empty stack
for each token t in tokens:
    if t is a number:
        push(S, t)
    else:
        if size(S) < 2:
            error "malformed expression"
        right = pop(S)
        left  = pop(S)
        push(S, apply(t, left, right))
if size(S) != 1:
    error "malformed expression"
result = pop(S)

go deeper

for a junior

Be ready to trace a short postfix sequence by hand and say what the stack holds after each token. Remember the rule that the value popped first is the right-hand operand.

for a middle

Explain why the order is forced rather than chosen: operands are pushed in reading order and the stack returns the most recent push. Say why commutative operators conceal the bug, and state the underflow and leftover checks.

for a senior

Show how you would catch this in review and in the test suite — one non-commutative operator with unequal operands — and cover malformed input, distinct unary tokens, and rounding rules when the values are money.

for a principal

Own the choice of representation. Argue when a formula engine should compile to a postfix or bytecode form once and evaluate many times versus walking a tree each recalculation, and what that means for auditability and for the team maintaining it.

## Postfix, and why it exists In postfix (reverse Polish) notation each operator follows its operands: `a b -` means a minus b, and `a b - c *` means `(a - b) * c`. The point of the notation is that it carries no parentheses and needs no precedence rules — the position of each operator already says exactly which values it consumes. That makes evaluation a single left-to-right scan with one stack, which is why formula engines, calculators and many bytecode machines evaluate in this form even when users type ordinary infix. Precedence and associativity are not gone; they were **resolved earlier**, during the conversion from infix to postfix. The evaluator inherits those decisions and must not undo them — and popping operands in the wrong order is exactly how it undoes them. ## The evaluation loop Scan the token sequence once: - **Number** — push it. - **Binary operator** — pop once into the right operand, pop again into the left operand, compute, push the result. - **End** — the single remaining value is the answer. The pop order falls straight out of LIFO. The operands were pushed in reading order, left then right, so the right operand is on top. Popping it first and the left operand second is not a convention you could flip; it is forced by the order the values arrived. ## Why the bug is so quiet Suppose someone writes the two pops the other way round. For addition and multiplication nothing happens — those operators are commutative, so swapping the arguments cannot change the result. A test suite built from sums and products passes completely. The failure only surfaces on subtraction, division, exponentiation, modulo, string concatenation, date differences, and any other non-commutative operation. Trace a spreadsheet formula that computes takehome as `(gross - fee) * rate`, in postfix `gross fee - rate *`, with gross = 120.00, fee = 45.50, rate = 0.9. Correct evaluation: push 120.00, push 45.50; on `-` pop 45.50 as right, pop 120.00 as left, compute 74.50, push it; push 0.9; on `*` pop 0.9, pop 74.50, compute **67.05**. Swapped pops: on `-` the operands become 45.50 minus 120.00 = -74.50, and the multiplication (being commutative) faithfully preserves the error to **-67.05**. The sign flip is the whole answer, and on a division the analogous bug returns a reciprocal — a rate of 0.9 becomes about 1.11 — which looks plausible enough to reach a report before anyone notices. This is why a review of an expression evaluator should ask for one test with a non-commutative operator and unequal operands. `9 4 -` must be 5, never -5; `9 3 /` must be 3, never one third. Equal operands are useless here: `4 4 -` is zero under both orders. ## The validation invariants A correct evaluator enforces two arity rules that candidates routinely skip: 1. **Underflow** — if a binary operator arrives with fewer than two values on the stack, the expression is malformed (`3 +`). Report it rather than reading past the bottom. 2. **Leftover** — when the scan ends, exactly one value must remain. Two or more means operands were never consumed (`3 4 5 +`), zero means there were no operands at all. Together these turn the evaluator into a cheap validator: a token sequence is well formed exactly when the running stack size never dips below the operator's arity and finishes at one. ## Unary operators and other traps Negation is the classic ambiguity. If unary minus and binary minus share a token, the evaluator cannot tell how many values to pop, and `5 3 -` becomes indistinguishable from a negation applied twice. The fix is upstream: emit a distinct token for unary negation during conversion, and let the evaluator pop one operand for it. The same reasoning generalises to n-ary functions — the token must carry its arity, or the stack cannot tell where the argument list starts. For a money-handling engine two more rules apply on top of the stack logic. Amounts should be evaluated in minor units or a decimal representation rather than binary floating point, so that repeated additions do not drift by fractions of a cent, and division needs an explicit rounding decision rather than whatever truncation happens to fall out. Neither is a stack question, but both live in the same function and both surface in a design discussion about a formula engine. ## What to say in the interview Name the rule (first pop is the right operand), name why (push order plus LIFO), name the reason it hides (commutativity masks it in the operators most tests use), and name the two arity invariants. That is a complete answer, and it demonstrates the habit the question is really probing: knowing which bugs your test data cannot catch.

  • What single test would you add to catch a swapped-operand bug that a suite of sums misses?
    One non-commutative operator with two unequal operands — a subtraction or a division where the arguments differ. `9 4 -` must yield 5 and `9 3 /` must yield 3; a swapped evaluator returns -5 and one third. Equal operands are worthless as a test, since `4 4 -` is zero under either order, and so is every addition or multiplication.
  • How does a postfix evaluator detect a malformed expression?
    By watching the stack size. A binary operator arriving with fewer than two values means operands are missing, and at end of scan exactly one value must remain — more than one means operands were never consumed, none means the input was empty. Those two checks make the evaluator a validator as well, with no extra pass.
  • Where do precedence and associativity get handled, if the evaluator has no notion of them?
    Upstream, in the conversion from infix to postfix. That step uses an operator stack and precedence comparisons to decide the operator order, and it bakes left-associativity of subtraction into the token sequence. The evaluator then just consumes the result, which is exactly why its own pop order must faithfully preserve left-versus-right.
  • How would you support unary negation without breaking the pop rule?
    Give it its own token during conversion so it is never confused with binary subtraction, and have the evaluator pop a single operand for it. More generally, each operator or function token must carry its arity, because the stack alone cannot say how many of the values below the top belong to this call.

saying these in an interview costs you the question

  • Pops the left operand first and applies it directly
  • Tests only with addition and multiplication
  • Claims operand order does not matter for any operator
  • Never checks the stack has two values before applying
  • Ignores the leftover-value check at end of scan
  • Treats unary minus as the binary operator token

context