The Shunting Yard Algorithm Explained Without the Hype
You type an expression like 3 + 4 * 2 into a calculator and it gives you 11. The reason it works is operator precedence. Postfix notation removes the need for precedence rules entirely by putting operators after their operands. 3 4 2 * + means the same thing, but a machine can evaluate it in a single left-to-right pass with no ambiguity. That is why every decent compiler or interpreter uses it internally.The standard approach is called the Shunting Yard algorithm, invented by Edsger Dijkstra in 1961. You scan the infix expression from left to right. Numbers and variables go straight into the output queue. Operators get pushed onto a stack. When you encounter another operator, you compare precedence and associativity. If the stack top has higher or equal precedence, you pop it to the output. You repeat that until the condition fails, then push the new operator. Parentheses are just markers: an opening parenthesis goes on the stack, and a closing parenthesis triggers pops until you hit the matching opener. Here is how a complete left-to-right scan looks with a real example. Expression: (A + B) * C - D / E
Output queue and operator stack evolve like this: A goes to output. B goes to output. + goes to stack. Parentheses sit there until the closing paren forces the + out. C goes to output. * goes to stack. D and E go to output. / compares to *, and since they have equal precedence, * pops first, then / goes on the stack. Finally the - beats the / on the stack, so / pops, then - goes on. At the end you pop whatever remains. The result is A B + C * D E / -. You can verify it by evaluating both sides. The infix version gives the same answer when you respect multiplication before addition. The postfix version does not need that rule at all.
I have written converters in Python, Java, C++, and JavaScript over the years. The algorithm itself is the same, but the quirks show up in edge cases. One thing that trips people up repeatedly is handling unary operators. The expression 5 + -3 looks simple, but a naive converter treats the second minus as a binary operator and produces garbage. You have to detect whether a minus sign is actually negation by checking if it appears at the start of the expression or immediately after an opening parenthesis or another operator. I wrote a helper function that looks at the token before the current one and classifies it accordingly. Without that check, your output breaks whenever someone writes something like (2 - -5) * 3. Another detail that is easy to miss involves floating point numbers with decimal points. If your tokenizer splits on spaces only, an expression like 3.14 + 2 stays intact because the dot is not whitespace. But if you tokenize character by character without tracking state, you might split 3.14 into 3, ., and 14, which destroys the number. I solved this by building a small lexical scanner that accumulates digits, dots, and exponent markers into a single token before handing it off to the conversion routine. Operator precedence deserves a proper table rather than a vague description. Here is the order most languages use, from lowest to highest binding strength:
Get the Full Details
- Assignment (=)
- Conditional (?:)
- Logical OR (||)
- Logical AND (&&)
- Equality (==, !=)
- Relational (<, >, <=, >=)
- Addition and subtraction (+, -)
- Multiplication, division, modulo (*, /, %)
- Exponentiation (^, )
- Unary minus and negation (-)
- Function call and member access ((), .)
Associativity matters too. Most operators are left-associative, meaning A - B - C parses as (A - B) - C. Exponentiation in many languages is right-associative, so 2 ^ 3 ^ 2 means 2 ^ (3 ^ 2), which equals 512, not 64. If your converter does not handle right-associativity correctly, you will get the wrong postfix result and the evaluation will be silently incorrect. The fix is straightforward: when comparing precedence on the stack, only pop operators with strictly higher precedence for right-associative operators. Pop equal precedence only for left-associative ones. There is a practical consequence to using postfix notation that most beginners do not think about. You cannot parse it back into infix without reintroducing parentheses, and even then the result may not match the original. A B C + * is unambiguous in postfix, but when you convert it back you have to decide whether it means A * (B + C) or (A * B) + C. The answer is the former, but the parentheses are required. This means postfix is not a lossless representation of infix structure. It is a lossless representation of evaluation order, which is the whole point. I ran into a messy situation once when I was working with expressions that included function calls and array indexing. Something like f(x) + g[h] * 2. The converter treated the parentheses inside f(x) as grouping parentheses and tried to pop operators prematurely. The workaround was to tokenize function names as a distinct category and skip the precedence comparison for opening parentheses that follow a function name. Same thing for square brackets. It added maybe thirty lines of code, but it prevented a whole class of bugs.
If you want a download link, here is a minimal Python implementation. It handles integers, decimals, basic operators, and parentheses. It does not handle unary operators, function calls, or multi-character variable names, so do not drop it into production without extending it. InfixToPostfixConverter.py Download the converter (Python)
The evaluation side is trivial. You do not even need the stack algorithm again. Just scan the postfix tokens left to right, push numbers onto a stack, and when you hit an operator, pop two values, apply the operator, and push the result back. Division by zero is the only error that bites you at runtime, so add a guard for that if you plan to use this in any real application. There are limits to what this approach can do well. It does not scale to expressions with side effects, like f() + g(), because the evaluation order in postfix is fixed but the original infix might imply a different calling sequence. It does not handle short-circuit evaluation in logical expressions. If you need those behaviors preserved, infix with a proper AST is the better choice, and postfix conversion is just an intermediate step anyway. The Shunting Yard algorithm is fast, usually completing in linear time, so converting to postfix and then evaluating is generally faster than parsing and evaluating an infix expression directly, but the difference is measured in microseconds unless you are processing millions of expressions per second. If you are building a calculator, a homework helper, or a simple expression evaluator for a script engine, postfix conversion is the right tool. If you are building a full programming language parser, skip it and go straight to a recursive descent or packrat parser that builds an abstract syntax tree. Both paths work, but they solve different problems.