Infix to Postfix Conversion Algorithm
The complete algorithm — seven rules, the precedence table, pseudocode you can translate into any language, a dry run, and the complexity analysis your exam will ask for.
The idea in one paragraph
The algorithm is Dijkstra's shunting-yard, published in 1961 and named after a railway shunting yard: incoming operators are shunted onto a siding (the stack) and released onto the main line (the output) in the right order. Operands are never ambiguous, so they are written out the instant they are read. Operators are always ambiguous — something stronger might follow — so they are held until the algorithm can prove their position is fixed.
The algorithm, step by step
Create an empty operator stack and an empty output list, then scan the infix expression from left to right, one token at a time.
- If the token is an operand, append it to the output. Its position in postfix is already final, so it never goes on the stack.
- If the token is an opening bracket, push it. It is a marker, not an operator — it will be discarded, never output.
- If the token is a closing bracket, pop to the output until the matching opening bracket is on top. Then pop that opening bracket and discard both. If the stack empties before an opening bracket is found, the expression is unbalanced.
- If the token is an operator, pop while the top of the stack outranks it. Keep popping to the output while the stack is non-empty, the top is not an opening bracket, and either the top has higher precedence, or the top has equal precedence and the incoming operator is left associative.
- Push the incoming operator. This happens immediately after the popping in the previous step finishes.
- When the scan finishes, pop every remaining operator to the output. Top down. If an opening bracket appears here, brackets were unbalanced.
- The output list is the postfix expression. It contains no brackets, and it can be evaluated left to right with a single value stack.
Operator priority and associativity
Priority — precedence — decides which operator wins when two of them compete for the same operand. Associativity resolves the tie when the priority is equal. These are the standard values used in data structures courses and in this site's converter.
| Priority | Operator | Name | Associativity |
|---|---|---|---|
| 3 — highest | ^ | Exponentiation | Right to left |
| 2 | * / % | Multiply, divide, modulo | Left to right |
| 1 — lowest | + - | Add, subtract | Left to right |
| n/a | ( ) | Brackets | Pushed as a marker; never emitted |
An opening bracket has the highest priority when it is being pushed — nothing
stops it going onto the stack — and the lowest when it is already on the
stack, because nothing pops it except its own closing bracket. Some textbooks list
this as two separate numbers, "in-stack priority" and "incoming priority". Treating
( as a special case rather than a number is simpler and gives identical
results.
All conversion rules in one table
| Token read | Condition | Action |
|---|---|---|
| Operand | always | Append to output |
| ( | always | Push onto stack |
| ) | always | Pop to output until (; discard both brackets |
| Operator | stack empty | Push |
| Operator | top is ( | Push |
| Operator | higher priority than top | Push |
| Operator | lower priority than top | Pop top to output, then re-test |
| Operator | equal priority, left associative | Pop top to output, then re-test |
| Operator | equal priority, right associative | Push without popping |
| End of input | stack non-empty | Pop everything to output |
Pseudocode
Language independent, and a direct transcription of the rules above. Every implementation on this site — including the C program and the JavaScript running the converter — is this same routine.
function infixToPostfix(expression):
stack = empty stack
output = empty list
for each token in expression:
if token is an operand:
output.append(token)
else if token == '(':
stack.push(token)
else if token == ')':
while stack is not empty and stack.peek() != '(':
output.append(stack.pop())
if stack is empty:
error "unbalanced: missing ("
stack.pop() // discard the '('
else if token is an operator:
while stack is not empty
and stack.peek() != '('
and ( priority(stack.peek()) > priority(token)
or ( priority(stack.peek()) == priority(token)
and isLeftAssociative(token) ) ):
output.append(stack.pop())
stack.push(token)
else:
error "invalid character"
while stack is not empty:
top = stack.pop()
if top == '(':
error "unbalanced: missing )"
output.append(top)
return output
Dry run
Tracing K + L - M * N + (O ^ P) * W / U / V * T + Q — the expression that
turns up in half the textbooks. Stack top is on the right.
| # | Token | Rule applied | Stack | Output |
|---|---|---|---|---|
| 1 | K | Operand | empty | K |
| 2 | + | Stack empty, push | + | K |
| 3 | L | Operand | + | K L |
| 4 | - | Equal priority, left assoc — pop +, push | - | K L + |
| 5 | M | Operand | - | K L + M |
| 6 | * | Higher priority, push | - * | K L + M |
| 7 | N | Operand | - * | K L + M N |
| 8 | + | Pop *, pop -, push | + | K L + M N * - |
| 9 | ( | Push marker | + ( | K L + M N * - |
| 10 | O | Operand | + ( | K L + M N * - O |
| 11 | ^ | Top is (, push | + ( ^ | K L + M N * - O |
| 12 | P | Operand | + ( ^ | K L + M N * - O P |
| 13 | ) | Pop ^, discard ( | + | K L + M N * - O P ^ |
| 14 | * | Higher than +, push | + * | K L + M N * - O P ^ |
| 15 | W | Operand | + * | K L + M N * - O P ^ W |
| 16 | / | Equal to *, left assoc — pop, push | + / | K L + M N * - O P ^ W * |
| 17 | U | Operand | + / | K L + M N * - O P ^ W * U |
| 18 | / | Equal, pop /, push | + / | … W * U / |
| 19 | V | Operand | + / | … W * U / V |
| 20 | * | Equal, pop /, push | + * | … W * U / V / |
| 21 | T | Operand | + * | … W * U / V / T |
| 22 | + | Pop *, pop +, push | + | … V / T * + |
| 23 | Q | Operand | + | … V / T * + Q |
| 24 | end | Pop + | empty | … V / T * + Q + |
The complete postfix result is:
Rows 18–22 are abbreviated with … for width;
open this expression in the converter
to see every cell in full.
The equal-precedence trap
This is the single most common bug. When the incoming operator has the same priority as the one on top of the stack, most people push. That is wrong for every left-associative operator.
| Infix | Correct postfix | Wrong (pushed on equal) | Means |
|---|---|---|---|
| A - B - C | A B - C - | A B C - - | A - (B - C) — different value |
| A / B / C | A B / C / | A B C / / | A / (B / C) — different value |
| A + B + C | A B + C + | A B C + + | Same value, but still the wrong tree |
Addition hides the bug because it is associative in arithmetic — the answer comes out the same. Subtraction and division expose it immediately, which is why exam questions almost always use them.
Right associativity and ^
Exponentiation is the one operator that groups right to left:
a ^ b ^ c means a ^ (b ^ c). So when a second ^
arrives and finds a ^ on the stack, it must not pop it.
a ^ b ^ c
a output: a
^ stack empty, push stack: ^
b output: a b
^ equal priority BUT right associative → push
stack: ^ ^
c output: a b c
end pop both output: a b c ^ ^
correct: a b c ^ ^ = a ^ (b ^ c)
wrong: a b ^ c ^ = (a ^ b) ^ c
In the pseudocode above this is handled by the single clause
and isLeftAssociative(token) on the equal-priority test. Remove it and
exponentiation breaks; hard-code it to true and everything else breaks.
Time and space complexity
| Measure | Cost | Reason |
|---|---|---|
| Time | O(n) | Each token is read once, pushed at most once and popped at most once. The
inner while loop looks quadratic but is amortised: it can only
pop what an earlier iteration pushed. |
| Auxiliary space | O(n) | The stack, in the worst case of deep nesting such as
((((a)))) or a long ^ chain. |
| Output space | O(n) | Postfix has the same operands and operators as the infix input, minus the brackets, so it is never longer. |
| Passes over input | 1 | A single left-to-right scan; no backtracking and no lookahead. |
Invalid input and edge cases
An algorithm that only works on well-formed input is half an algorithm. These are the cases worth handling explicitly, and how each one is detected.
| Input | Problem | Detected when |
|---|---|---|
| A + B) | Extra closing bracket | Popping for ) empties the stack without finding ( |
| (A + B | Unclosed bracket | A ( is still on the stack at the final flush |
| A + * B | Two operators in a row | Track whether the next token must be an operand; * arrives when one is expected |
| A B | Missing operator | An operand arrives when an operator is expected |
| A + | Trailing operator | Input ends while an operand is still expected |
| () | Empty brackets | ) arrives immediately after ( |
| 2(3 + 4) | Implied multiplication | ( arrives when an operator is expected |
A single boolean — "the next token must be an operand" — flipped by each token type catches every row except the bracket cases, which the stack itself catches. The converter on this site uses exactly that check, which is why it explains what is wrong instead of silently producing nonsense.
Adapting it for prefix
The same routine produces prefix (Polish notation) with three changes wrapped around it:
- Reverse the infix expression, swapping every
(for)and vice versa. - Run the algorithm, but flip the associativity test — pop only on strictly higher priority for left-associative operators, and on equal priority for right-associative ones.
- Reverse the result. That is the prefix expression.
So A + B * C becomes + A * B C. The converter on the
homepage shows the prefix form alongside the postfix one for every expression you
enter.
Frequently asked questions
What is the algorithm to convert infix to postfix?
Dijkstra's shunting-yard algorithm. Scan the infix expression left to right using an operator stack: append operands to the output, push opening brackets, pop to the output until the matching bracket on a closing bracket, and for each operator pop while the stack top has higher precedence — or equal precedence and the incoming operator is left associative — before pushing it. At the end, pop the whole stack to the output.
Which operator has the highest priority in infix to postfix conversion?
Exponentiation ^ is highest, then multiplication, division and
modulo * / %, then addition and subtraction + -.
Brackets are not given a priority number in this treatment — an opening bracket
is always pushed and is only removed by its matching closing bracket.
What is the time complexity of infix to postfix conversion?
O(n) time and O(n) auxiliary space for n tokens. Every token is read once and every operator is pushed and popped at most once, so the inner popping loop is amortised constant despite looking like a nested loop.
Why is it called the shunting-yard algorithm?
Edsger Dijkstra named it after a railway shunting yard. The input expression is a train of carriages arriving on one track; operators are shunted onto a siding (the stack) and released back onto the main line (the output) in a different order, which is precisely what the algorithm does with operators.
Do brackets appear in the postfix output?
No. Brackets exist only to override precedence in infix notation, and postfix has no precedence to override — the order of symbols alone determines evaluation order. Opening brackets are pushed as markers and discarded when their closing bracket arrives.
What is the difference between the infix to postfix and infix to prefix algorithms?
The core loop is identical. For prefix you reverse the input first (swapping the brackets), flip the associativity comparison so left-associative operators pop only on strictly higher precedence, and reverse the output at the end.
How do you handle unary minus?
Give it its own token — commonly written u- or ~ — with
precedence above ^ and right associativity, so it pops nothing and
binds to the operand on its right. Simpler implementations, including this site's
converter, instead fold the sign into the numeric literal that follows it and ask
you to rewrite -x as (0 - x).