Back to Stack & Queue
Q6Question 6 of 20

Evaluate Postfix Expression: stack-based evaluation

Key Concepts
  • Push numbers onto a stack; on each operator pop two operands, compute, and push the result back.
  • The first pop is the right operand and the second pop is the left operand, which matters for subtraction and division.
  • Use BigDecimal with explicit scale and RoundingMode for banking money to avoid non-terminating decimal exceptions.
  • A well-formed postfix expression leaves exactly one value on the stack at the end.
  • Time is O(n) with one scan; space is O(n) for the stack.
  • Postfix has no parentheses and no precedence rules because the operator order in the string encodes the evaluation order.

Definition

Push numbers, and on each operator pop two operands, compute, and push the result back; the last value on the stack is the answer.
  • Postfix (Reverse Polish Notation) – operators come after their operands, e.g. 3 4 + 2 * means (3 + 4) * 2.
  • Stack evaluation – scan tokens left to right, push numbers, and on each operator pop two operands, apply the operation, and push the result.
  • Operand order – the first pop is the right operand (b), the second is the left (a); compute a op b.
  • No precedence – the token order already encodes the evaluation order, so no parentheses or precedence rules are needed.
  • Banking – use BigDecimal with explicit scale and RoundingMode for exact money arithmetic.
  • Validation – a well-formed expression leaves exactly one value on the stack.

Rajesh computes a fee formula written in a special order where the amounts come first and the operation comes last, like "base tax add rate multiply." He keeps a tray of numbers. Every time he reads a number, he drops it on the tray. When he reads an operation like "add," he takes the top two numbers off the tray, adds them, and puts the result back. At the end there is exactly one number on the tray, and that is the final fee.

Understanding the Concepts

What is postfix evaluation?

Postfix notation, also called Reverse Polish Notation (RPN), places operators after their operands. The expression 3 4 + 2 * means add 3 and 4 first, then multiply by 2, giving 14. Because the operator order in the string already encodes the evaluation order, there are no parentheses and no precedence rules.

JAVA
package com.bank.service;

import java.util.ArrayDeque;
import java.util.Deque;

public class PostfixEvaluator {
    public static int evaluate(String[] tokens) {
        Deque<Integer> stack = new ArrayDeque<>();
        for (String token : tokens) {
            switch (token) {
                case "+": { int b = stack.pop(); int a = stack.pop(); stack.push(a + b); break; }
                case "-": { int b = stack.pop(); int a = stack.pop(); stack.push(a - b); break; }
                case "*": { int b = stack.pop(); int a = stack.pop(); stack.push(a * b); break; }
                case "/": { int b = stack.pop(); int a = stack.pop(); stack.push(a / b); break; }
                default: stack.push(Integer.parseInt(token));
            }
        }
        return stack.pop();
    }

    public static void main(String[] args) {
        // (3 + 4) * 2 = 14
        String[] expr = {"3", "4", "+", "2", "*"};
        System.out.println(evaluate(expr)); // 14
    }
}
TEXT
14

Why does the stack make this work?

Each operator needs exactly two operands. The stack holds operands waiting for their operator. When an operator arrives, the two most recent operands are on top of the stack. Popping them, computing, and pushing the result makes that result available as an operand for the next operator. The nesting is handled automatically.

JAVA
package com.bank.core;

import java.util.ArrayDeque;
import java.util.Deque;

public class StackTrace {
    public static void main(String[] args) {
        String[] tokens = {"100000", "0.02", "*", "500", "+"};
        Deque<String> stack = new ArrayDeque<>();
        for (String t : tokens) {
            if (t.matches("-?[0-9.]+")) {
                stack.push(t);
                System.out.println("Push " + t + " -> " + stack);
            } else {
                String b = stack.pop();
                String a = stack.pop();
                double result = switch (t) {
                    case "*" -> Double.parseDouble(a) * Double.parseDouble(b);
                    case "+" -> Double.parseDouble(a) + Double.parseDouble(b);
                    default -> 0;
                };
                stack.push(String.valueOf(result));
                System.out.println(a + " " + t + " " + b + " = " + result + " -> " + stack);
            }
        }
    }
}
TEXT
Push 100000 -> [100000]
Push 0.02 -> [0.02, 100000]
100000 * 0.02 = 2000.0 -> [2000.0]
Push 500 -> [500, 2000.0]
2000.0 + 500 = 2500.0 -> [2500.0]

Why does operand order matter?

The first pop is the right operand (b) because it was pushed most recently. The second pop is the left operand (a). For commutative operators like + and * the order does not matter, but for subtraction and division it does. Computing a - b versus b - a gives different results, and swapping them silently produces wrong answers.

JAVA
package com.bank.core;

import java.util.ArrayDeque;
import java.util.Deque;

public class OperandOrder {
    public static void main(String[] args) {
        // Expression: 10 3 - means 10 - 3 = 7, not 3 - 10 = -7
        Deque<Integer> stack = new ArrayDeque<>();
        stack.push(10); // a
        stack.push(3);  // b

        int b = stack.pop(); // 3 (right operand)
        int a = stack.pop(); // 10 (left operand)

        System.out.println("Correct: a - b = " + (a - b)); // 7
        System.out.println("Wrong:   b - a = " + (b - a)); // -7
    }
}
TEXT
Correct: a - b = 7
Wrong:   b - a = -7

How do you handle banking amounts with BigDecimal?

For money, use BigDecimal and its arithmetic methods. Division must supply an explicit scale and RoundingMode to avoid a non-terminating decimal exception when the result has infinite decimal places, like 1 divided by 3.

JAVA
package com.bank.service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayDeque;
import java.util.Deque;

public class PostfixBigDecimal {
    public static BigDecimal evaluate(String[] tokens) {
        Deque<BigDecimal> stack = new ArrayDeque<>();
        for (String token : tokens) {
            switch (token) {
                case "+": { BigDecimal b = stack.pop(); BigDecimal a = stack.pop(); stack.push(a.add(b)); break; }
                case "-": { BigDecimal b = stack.pop(); BigDecimal a = stack.pop(); stack.push(a.subtract(b)); break; }
                case "*": { BigDecimal b = stack.pop(); BigDecimal a = stack.pop(); stack.push(a.multiply(b)); break; }
                case "/": { BigDecimal b = stack.pop(); BigDecimal a = stack.pop();
                    stack.push(a.divide(b, 2, RoundingMode.HALF_UP)); break; }
                default: stack.push(new BigDecimal(token));
            }
        }
        return stack.pop();
    }

    public static void main(String[] args) {
        // 100000 * 0.02 + 500 = 2500.00
        String[] feeFormula = {"100000", "0.02", "*", "500", "+"};
        System.out.println("Fee: " + evaluate(feeFormula));

        // 1000 / 3 = 333.33 (rounded)
        String[] divFormula = {"1000", "3", "/"};
        System.out.println("Division: " + evaluate(divFormula));
    }
}
TEXT
Fee: 2500.00
Division: 333.33

What happens with an invalid expression?

A well-formed postfix expression leaves exactly one value on the stack. If more than one value remains, there are extra operands. If the stack has fewer than two operands when an operator arrives, the expression is malformed. Both cases should be detected and rejected.

JAVA
package com.bank.service;

import java.util.ArrayDeque;
import java.util.Deque;

public class PostfixValidation {
    public static String evaluateWithValidation(String[] tokens) {
        Deque<Integer> stack = new ArrayDeque<>();
        for (String token : tokens) {
            if (token.matches("-?\\d+")) {
                stack.push(Integer.parseInt(token));
            } else {
                if (stack.size() < 2) return "Error: not enough operands for " + token;
                int b = stack.pop();
                int a = stack.pop();
                switch (token) {
                    case "+" -> stack.push(a + b);
                    case "-" -> stack.push(a - b);
                    case "*" -> stack.push(a * b);
                    case "/" -> {
                        if (b == 0) return "Error: division by zero";
                        stack.push(a / b);
                    }
                    default -> { return "Error: unknown operator " + token; }
                }
            }
        }
        if (stack.size() != 1) return "Error: " + stack.size() + " values remain";
        return "Result: " + stack.pop();
    }

    public static void main(String[] args) {
        System.out.println(evaluateWithValidation(new String[]{"3", "4", "+"}));        // Result: 7
        System.out.println(evaluateWithValidation(new String[]{"3", "+"}));             // Error
        System.out.println(evaluateWithValidation(new String[]{"3", "4", "5", "+"}));   // Error: 2 values remain
        System.out.println(evaluateWithValidation(new String[]{"10", "0", "/"}));       // Error: division by zero
    }
}
TEXT
Result: 7
Error: not enough operands for +
Error: 2 values remain
Error: division by zero

What are common mistakes?

The most common mistake is swapping the operand order for subtraction and division. Another is using double for money, which introduces floating-point rounding errors. A third is not handling division by zero, which throws ArithmeticException for integer division and produces Infinity for doubles.

JAVA
package com.bank.core;

public class PostfixMistakes {
    public static void main(String[] args) {
        // Mistake 1: wrong operand order
        // "10 3 -" should be 10 - 3 = 7, not 3 - 10 = -7
        System.out.println("Always: first pop = right, second pop = left");

        // Mistake 2: double for money
        double wrong = 0.1 + 0.2;
        System.out.println("double: 0.1 + 0.2 = " + wrong); // 0.30000000000000004

        java.math.BigDecimal correct = new java.math.BigDecimal("0.1")
                .add(new java.math.BigDecimal("0.2"));
        System.out.println("BigDecimal: 0.1 + 0.2 = " + correct); // 0.3
    }
}
TEXT
Always: first pop = right, second pop = left
double: 0.1 + 0.2 = 0.30000000000000004
BigDecimal: 0.1 + 0.2 = 0.3

How is this applied in banking?

At National Bank of India, the fee-calculation engine stores pricing formulas in postfix form because they are compact and unambiguous. A formula like 100000 0.02 * 500 + computes 2% of the principal plus a flat charge. Storing formulas as data lets the pricing team change fee logic without redeploying code, and the stack evaluator guarantees exact, correctly rounded amounts using BigDecimal.

JAVA
package com.bank.service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayDeque;
import java.util.Deque;

public class FeeEngine {
    public static BigDecimal computeFee(String[] formula) {
        Deque<BigDecimal> stack = new ArrayDeque<>();
        for (String token : formula) {
            switch (token) {
                case "+" -> { BigDecimal b = stack.pop(); stack.push(stack.pop().add(b)); }
                case "-" -> { BigDecimal b = stack.pop(); stack.push(stack.pop().subtract(b)); }
                case "*" -> { BigDecimal b = stack.pop(); stack.push(stack.pop().multiply(b)); }
                case "/" -> { BigDecimal b = stack.pop(); stack.push(stack.pop().divide(b, 2, RoundingMode.HALF_UP)); }
                default -> stack.push(new BigDecimal(token));
            }
        }
        return stack.pop();
    }

    public static void main(String[] args) {
        // 2% of 100000 + flat 500 = 2500.00
        String[] formula = {"100000", "0.02", "*", "500", "+"};
        System.out.println("Processing fee: " + computeFee(formula));

        // (principal * rate - discount) = (500000 * 0.015 - 1000) = 6500.00
        String[] formula2 = {"500000", "0.015", "*", "1000", "-"};
        System.out.println("Loan processing fee: " + computeFee(formula2));
    }
}
TEXT
Processing fee: 2500.00
Loan processing fee: 6500.000

What should a senior engineer know?

Postfix evaluation is one direction of the Shunting-Yard algorithm. The full pipeline is: parse infix to postfix using the Shunting-Yard algorithm, then evaluate postfix with the stack. The Shunting-Yard handles precedence and associativity during conversion, so the evaluator itself never needs to know about them. For production fee engines, this two-phase approach keeps the evaluator simple and the precedence logic isolated.

JAVA
package com.bank.core;

public class SeniorNotes {
    public static void main(String[] args) {
        System.out.println("Full pipeline: infix -> Shunting-Yard -> postfix -> stack eval");
        System.out.println("Shunting-Yard handles precedence and associativity");
        System.out.println("Stack evaluator is always O(n) with no precedence logic");
        System.out.println("Production: store formulas as postfix data, evaluate on demand");
    }
}
TEXT
Full pipeline: infix -> Shunting-Yard -> postfix -> stack eval
Shunting-Yard handles precedence and associativity
Stack evaluator is always O(n) with no precedence logic
Production: store formulas as postfix data, evaluate on demand

Visual Overview

DIAGRAM
  Postfix Evaluation: "100000 0.02 * 500 +"
  ───────────────────────────────────────────

  Token     Action               Stack
  ───────   ────────────────     ──────────────
  100000    push                 [100000]
  0.02      push                 [0.02, 100000]
  *         pop 0.02, 100000     [2000]
            compute 100000*0.02
            push 2000
  500       push                 [500, 2000]
  +         pop 500, 2000        [2500]
            compute 2000+500
            push 2500

  Result: 2500 (one value remains)
DIAGRAM
  Operand Order on Pop
  ─────────────────────
  Stack top → bottom: [ b | a | ... ]

  First pop  → b (right operand)
  Second pop → a (left operand)
  Compute: a OP b

  For "10 3 -":  a=10, b=3 → 10 - 3 = 7

Key Takeaway

Evaluating a postfix expression is the cleanest demonstration of why Reverse Polish Notation exists: operators follow their operands, so the token order already encodes the evaluation order with no parentheses and no precedence rules. My approach is a single left-to-right scan with a stack. Every number I push, and every operator triggers popping the top two values, applying the operation, and pushing the result. When the scan ends, exactly one value remains.

The detail I am always careful about is operand order. The first pop is the right operand because it was pushed most recently, and the second pop is the left operand. For subtraction and division, swapping these silently produces wrong answers, so I compute left-operator-right deliberately. For a banking fee engine this is not cosmetic; a mis-ordered subtraction would quietly overcharge or undercharge a customer.

Because this is money, I keep BigDecimal on the stack rather than primitives, and for division I always supply an explicit scale and RoundingMode.HALF_UP so I get correctly rounded amounts instead of a non-terminating-decimal exception. I also validate the expression: a well-formed postfix string leaves exactly one value, so if values are left over or run out mid-scan, I reject it rather than returning something meaningless.

In my project at the National Bank of India, the fee-calculation engine stores pricing formulas in postfix form because they are compact and unambiguous. Storing formulas as data lets the pricing team change fee logic without redeploying code, and the stack evaluator guarantees exact, correctly rounded amounts in a single O(n) pass.

Remember one sentence: push numbers, pop two on every operator with the correct left-right order, and the single remaining value is the answer.

---

Share
Use ← → arrow keys to navigate