Stacks & Queues
A stack hands back the newest item first and a queue the oldest, both in O(1). That order alone solves brackets, expression evaluation and BFS.
How It Works
Stacks and queues are lists you may only touch at the ends. A stack adds and removes at the same end, so the last thing in is the first thing out (LIFO), like a pile of plates. A queue adds at the back and removes from the front, so things leave in the order they arrived (FIFO), like a line at a counter. That restriction is the point: every operation is O(1), and the order things come out often does the work for you.
stack: push and pop at the top
push pop
| ^
v |
| |
| 3 | <- top
| 2 |
| 1 |
+-----------+
queue: enqueue at the back, dequeue at the front
dequeue <- [ 1 | 2 | 3 ] <- enqueue
front backA Stack and a Queue, Side by Side
Step 1 of 8
stack
stack order
queue
queue order
- Just added or removed
- Handed back
- Behind head, already dequeued
Both start empty. You'll add 1, 2 and 3 to each, then take everything back out. They differ only in which end you remove from.
All 8 steps as text
- Both start empty. You'll add
1,2and3to each, then take everything back out. They differ only in which end you remove from. - Push
1onto the stack and enqueue it at the back of the queue. Both are O(1), since nothing else moves. - Add
2. It goes on top of1in the stack and behind1in the queue. - Add
3. Both hold1, 2, 3now, but the stack removes from the end you just added to, and the queue from the other end. - Pop the top,
3, the newest value. The queue's front is the oldest,1. Dequeue it by movingheadpast it, which is O(1) with no shifting. - Next, the stack gives back
2and the queue gives back2. Each time, the stack takes the newest value left and the queue the oldest. - Last out:
1from the stack, the first value you pushed, and3from the queue, the last one you enqueued. - Both are empty, since
headis past the queue's last slot. The stack handed back[3, 2, 1], newest first, and the queue[1, 2, 3], in arrival order.
Reach for a stack when the most recent unfinished thing is the one you have to deal with next: matching brackets, undo history, evaluating an expression, or exploring as deep as you can before backing up. The call stack is itself a stack, which is why any recursive function can be rewritten as a loop with an explicit one. Reach for a queue when things must be handled in the order they arrive: breadth-first search, task scheduling, or buffering a stream.
Every language gives you both, but check which end is cheap. Adding or removing at the end of an array is O(1), so arrays make good stacks. Removing from the front is where it goes wrong. JavaScript's shift() and Python's list.pop(0) move every remaining element down one slot, so each call is O(n). A queue needs a structure built for it, or an index that marks where the front is.
When to Reach for It
- Brackets, tags or anything else that opens and closes in nested order.
- You need the most recent item that hasn't been resolved yet, as in undo or simplifying a file path.
- There's an expression to evaluate, especially one in postfix (Reverse Polish) form.
- Things must be handled in arrival order, or level by level, as in breadth-first search.
- You want to replace recursion with a loop. An explicit stack does the call stack's job.
Complexity
| Operation | Time | Space |
|---|---|---|
| Push, pop or peek on a stack | O(1), amortized for array-backed stacks | O(n) for n items |
| Enqueue, dequeue or peek on a queue or deque | O(1), amortized for array-backed queues | O(n) for n items |
Remove from the front of an array (shift(), pop(0)) | O(n) | O(1) |
| Search for a value | O(n) | O(1) |
The Template
Stack and Queue Basics
Both functions add the same values and differ only in which end they remove from. The stack hands [1, 2, 3] back as [3, 2, 1], and the queue hands it back unchanged. Each loop peeks first and then removes, the same check-then-act step you'll use whenever a decision depends on the top or the front.
Here's what each language gives you:
- TypeScript: an array is the stack, with
push,popandstack[stack.length - 1]to peek. For a queue,pushonto an array and advance aheadindex instead of callingshift(). - Python: a
listis the stack, withappend,pop()andstack[-1]. For a queue, usecollections.dequewithappend,popleft()andqueue[0]. - Go: slices do both. Pop with
stack[:len(stack)-1]and dequeue withqueue[1:], which reslices without copying anything. - C#:
Stack<T>hasPush,PopandPeek, andQueue<T>hasEnqueue,DequeueandPeek. - Java:
ArrayDequedoes both. Usepush,popandpeekas a stack, oroffer,pollandpeekas a queue. Java's own docs recommend it over the oldStackclass, and it's usually faster thanLinkedListas a queue.
Why not shift()? A JavaScript array keeps its elements at indices 0 to length - 1 with no gaps. shift() removes index 0, so every remaining element has to move down one slot to keep that true, which is n - 1 moves per call. Dequeuing n items that way costs O(n²) in total. Engines speed up some cases, mostly small arrays, but you can't count on it. Advancing a head index is O(1) every time. Python's list.pop(0) has the same problem, which is why deque exists.
Time O(1) per operationSpace O(n)
from collections import deque
def stack_order(values: list[int]) -> list[int]:
"""A stack adds and removes at the same end, so values come back newest first.
Pushes every value, then peeks at and pops the top until the stack is empty."""
stack: list[int] = []
for value in values:
stack.append(value) # Push onto the top (the end).
order = []
while stack:
top = stack[-1] # Peek: read the top without removing it.
stack.pop() # Pop: remove the top. O(1), since nothing else moves.
order.append(top)
return order
def queue_order(values: list[int]) -> list[int]:
"""A queue adds at the back and removes from the front, so values come back in
arrival order. Enqueues every value, then peeks at and dequeues the front."""
queue: deque[int] = deque() # list.pop(0) would be O(n). A deque pops either end in O(1).
for value in values:
queue.append(value) # Enqueue at the back.
order = []
while queue:
front = queue[0] # Peek: read the front without removing it.
queue.popleft() # Dequeue: remove the front. O(1).
order.append(front)
return order
if __name__ == "__main__":
print(stack_order([1, 2, 3])) # [3, 2, 1]
print(queue_order([1, 2, 3])) # [1, 2, 3]
print(stack_order([])) # []
print(queue_order([])) # []// A stack adds and removes at the same end, so values come back newest first.
// Pushes every value, then peeks at and pops the top until the stack is empty.
export function stackOrder(values: number[]): number[] {
const stack: number[] = [];
for (const value of values) stack.push(value); // Push onto the top (the end).
const order: number[] = [];
while (stack.length > 0) {
const top = stack[stack.length - 1]; // Peek: read the top without removing it.
stack.pop(); // Pop: remove the top. O(1), since nothing else moves.
order.push(top);
}
return order;
}
// A queue adds at the back and removes from the front, so values come back in
// arrival order. Enqueues every value, then peeks at and dequeues the front.
export function queueOrder(values: number[]): number[] {
const queue: number[] = [];
let head = 0; // Index of the front. queue.shift() would be O(n), so move this instead.
for (const value of values) queue.push(value); // Enqueue at the back.
const order: number[] = [];
while (head < queue.length) {
const front = queue[head]; // Peek: read the front without removing it.
head++; // Dequeue: step past the front. O(1).
order.push(front);
}
return order;
}
console.log(JSON.stringify(stackOrder([1, 2, 3]))); // [3,2,1]
console.log(JSON.stringify(queueOrder([1, 2, 3]))); // [1,2,3]
console.log(JSON.stringify(stackOrder([]))); // []
console.log(JSON.stringify(queueOrder([]))); // []package main
import "fmt"
// stackOrder pushes every value, then peeks at and pops the top until the
// stack is empty. A stack adds and removes at the same end, so values come
// back newest first.
func stackOrder(values []int) []int {
stack := []int{}
for _, value := range values {
stack = append(stack, value) // Push onto the top (the end).
}
order := []int{}
for len(stack) > 0 {
top := stack[len(stack)-1] // Peek: read the top without removing it.
stack = stack[:len(stack)-1] // Pop: shrink the slice by one. O(1).
order = append(order, top)
}
return order
}
// queueOrder enqueues every value, then peeks at and dequeues the front until
// the queue is empty. A queue adds at the back and removes from the front, so
// values come back in arrival order.
func queueOrder(values []int) []int {
queue := []int{}
for _, value := range values {
queue = append(queue, value) // Enqueue at the back.
}
order := []int{}
for len(queue) > 0 {
front := queue[0] // Peek: read the front without removing it.
queue = queue[1:] // Dequeue: reslice past the front. O(1), nothing is copied.
order = append(order, front)
}
return order
}
func main() {
fmt.Println(stackOrder([]int{1, 2, 3})) // [3 2 1]
fmt.Println(queueOrder([]int{1, 2, 3})) // [1 2 3]
fmt.Println(stackOrder([]int{})) // []
fmt.Println(queueOrder([]int{})) // []
}using System;
using System.Collections.Generic;
public class Solution
{
// A stack adds and removes at the same end, so values come back newest first.
// Pushes every value, then peeks at and pops the top until the stack is empty.
public static List<int> StackOrder(int[] values)
{
var stack = new Stack<int>();
foreach (int value in values) stack.Push(value); // Push onto the top.
var order = new List<int>();
while (stack.Count > 0)
{
int top = stack.Peek(); // Peek: read the top without removing it.
stack.Pop(); // Pop: remove the top. O(1), since nothing else moves.
order.Add(top);
}
return order;
}
// A queue adds at the back and removes from the front, so values come back in
// arrival order. Enqueues every value, then peeks at and dequeues the front.
public static List<int> QueueOrder(int[] values)
{
var queue = new Queue<int>();
foreach (int value in values) queue.Enqueue(value); // Enqueue at the back.
var order = new List<int>();
while (queue.Count > 0)
{
int front = queue.Peek(); // Peek: read the front without removing it.
queue.Dequeue(); // Dequeue: remove the front. O(1).
order.Add(front);
}
return order;
}
public static void Main()
{
Console.WriteLine(Format(StackOrder(new[] { 1, 2, 3 }))); // [3, 2, 1]
Console.WriteLine(Format(QueueOrder(new[] { 1, 2, 3 }))); // [1, 2, 3]
Console.WriteLine(Format(StackOrder(new int[0]))); // []
Console.WriteLine(Format(QueueOrder(new int[0]))); // []
}
static string Format(List<int> values) => "[" + string.Join(", ", values) + "]";
}import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
public class Solution {
// A stack adds and removes at the same end, so values come back newest first.
// Pushes every value, then peeks at and pops the top until the stack is empty.
static List<Integer> stackOrder(int[] values) {
Deque<Integer> stack = new ArrayDeque<>();
for (int value : values) stack.push(value); // Push onto the top.
List<Integer> order = new ArrayList<>();
while (!stack.isEmpty()) {
int top = stack.peek(); // Peek: read the top without removing it.
stack.pop(); // Pop: remove the top. O(1), since nothing else moves.
order.add(top);
}
return order;
}
// A queue adds at the back and removes from the front, so values come back in
// arrival order. Enqueues every value, then peeks at and dequeues the front.
static List<Integer> queueOrder(int[] values) {
Deque<Integer> queue = new ArrayDeque<>();
for (int value : values) queue.offer(value); // Enqueue at the back.
List<Integer> order = new ArrayList<>();
while (!queue.isEmpty()) {
int front = queue.peek(); // Peek: read the front without removing it.
queue.poll(); // Dequeue: remove the front. O(1).
order.add(front);
}
return order;
}
public static void main(String[] args) {
System.out.println(stackOrder(new int[] {1, 2, 3})); // [3, 2, 1]
System.out.println(queueOrder(new int[] {1, 2, 3})); // [1, 2, 3]
System.out.println(stackOrder(new int[] {})); // []
System.out.println(queueOrder(new int[] {})); // []
}
}Worked Problems
Each solution stays folded until you open it, so you can try the problem first. Every one is a complete program in Python, TypeScript, Go, C# and Java, run and checked against its expected output.
Problem
You're given a string made only of the characters ()[]{}. Return whether it's balanced: every opening bracket is closed by a bracket of the same type, and pairs close in the right order, so they nest instead of crossing.
Example: ([]){} is valid. ([)] is not, because ) arrives while [ is still open.
Show SolutionHide Solution
Approach
The most recently opened bracket has to close first, which is exactly the order a stack gives you. Push each opener. When a closer arrives, pop the top and check that it's the matching opener. If the stack is empty, or the top is the wrong type, the string is invalid.
char stack
( (
[ ( [
] ( ] closes [
) empty ) closes (
{ {
} empty } closes {
end empty validMatching Brackets With a Stack
Step 1 of 8
s
stack
- Current character
- Open, waiting for its closer
- Matched pair
Read s left to right with an empty stack. Each opener waits on the stack until its closer arrives.
All 8 steps as text
- Read
sleft to right with an empty stack. Each opener waits on the stack until its closer arrives. (is an opener, so push it. It waits for a).[is an opener too. It goes on top of(, so it has to close first.]is a closer. Pop the top,[: it matches, so the pair closes.(is on top again.)is a closer. Pop the top,(: it matches, so the pair closes. Nothing is open now.{is an opener, so push it. It waits for a}.}is a closer. Pop the top,{: it matches, so the pair closes. Nothing is open now.- The scan is done and the stack is empty, so every opener was closed in order.
([]){}is valid: returntrue.
Two failures are easy to miss. A closer can arrive when nothing is open, as in ], so check for an empty stack before you pop. And openers can be left over at the end, as in ((, so the string is only valid if the stack ends up empty.
Time O(n)Space O(n)
# Each closing bracket and the opener it must match.
PAIRS = {")": "(", "]": "[", "}": "{"}
def is_valid(s: str) -> bool:
"""Whether every bracket in `s` is closed by the same type, in the right order."""
stack: list[str] = []
for ch in s:
opener = PAIRS.get(ch)
if opener is None:
stack.append(ch) # An opener waits on the stack for its closer.
elif not stack or stack.pop() != opener:
return False # A closer must match the most recent unclosed opener.
return not stack # Anything left was never closed.
if __name__ == "__main__":
print(is_valid("([]){}")) # True
print(is_valid("([)]")) # False
print(is_valid("((")) # False
print(is_valid("]")) # False// Each closing bracket and the opener it must match.
const PAIRS = new Map([
[")", "("],
["]", "["],
["}", "{"],
]);
// Whether every bracket in s is closed by the same type, in the right order.
export function isValid(s: string): boolean {
const stack: string[] = [];
for (const ch of s) {
const opener = PAIRS.get(ch);
if (opener === undefined) {
stack.push(ch); // An opener waits on the stack for its closer.
} else if (stack.length === 0 || stack.pop() !== opener) {
return false; // A closer must match the most recent unclosed opener.
}
}
return stack.length === 0; // Anything left was never closed.
}
console.log(isValid("([]){}")); // true
console.log(isValid("([)]")); // false
console.log(isValid("((")); // false
console.log(isValid("]")); // falsepackage main
import "fmt"
// pairs maps each closing bracket to the opener it must match.
var pairs = map[rune]rune{')': '(', ']': '[', '}': '{'}
// isValid reports whether every bracket in s is closed by the same type, in
// the right order.
func isValid(s string) bool {
stack := []rune{}
for _, ch := range s {
opener, isCloser := pairs[ch]
if !isCloser {
stack = append(stack, ch) // An opener waits on the stack for its closer.
} else if len(stack) == 0 || stack[len(stack)-1] != opener {
return false // A closer must match the most recent unclosed opener.
} else {
stack = stack[:len(stack)-1]
}
}
return len(stack) == 0 // Anything left was never closed.
}
func main() {
fmt.Println(isValid("([]){}")) // true
fmt.Println(isValid("([)]")) // false
fmt.Println(isValid("((")) // false
fmt.Println(isValid("]")) // false
}using System;
using System.Collections.Generic;
public class Solution
{
// Each closing bracket and the opener it must match.
static readonly Dictionary<char, char> Pairs = new() { [')'] = '(', [']'] = '[', ['}'] = '{' };
// Whether every bracket in s is closed by the same type, in the right order.
public static bool IsValid(string s)
{
var stack = new Stack<char>();
foreach (char ch in s)
{
if (!Pairs.TryGetValue(ch, out char opener))
{
stack.Push(ch); // An opener waits on the stack for its closer.
}
else if (stack.Count == 0 || stack.Pop() != opener)
{
return false; // A closer must match the most recent unclosed opener.
}
}
return stack.Count == 0; // Anything left was never closed.
}
public static void Main()
{
Console.WriteLine(IsValid("([]){}")); // True
Console.WriteLine(IsValid("([)]")); // False
Console.WriteLine(IsValid("((")); // False
Console.WriteLine(IsValid("]")); // False
}
}import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
public class Solution {
// Each closing bracket and the opener it must match.
static final Map<Character, Character> PAIRS = Map.of(')', '(', ']', '[', '}', '{');
// Whether every bracket in s is closed by the same type, in the right order.
static boolean isValid(String s) {
Deque<Character> stack = new ArrayDeque<>();
for (char ch : s.toCharArray()) {
Character opener = PAIRS.get(ch);
if (opener == null) {
stack.push(ch); // An opener waits on the stack for its closer.
} else if (stack.isEmpty() || !stack.pop().equals(opener)) {
return false; // A closer must match the most recent unclosed opener.
}
}
return stack.isEmpty(); // Anything left was never closed.
}
public static void main(String[] args) {
System.out.println(isValid("([]){}")); // true
System.out.println(isValid("([)]")); // false
System.out.println(isValid("((")); // false
System.out.println(isValid("]")); // false
}
}Problem
In Reverse Polish Notation every operator comes after its two operands, so 3 4 + means 3 + 4, and parentheses are never needed. Given the tokens of a valid expression that uses +, -, * and / on integers, return its value. Division truncates toward zero.
Example: the tokens 2 1 + 3 * mean (2 + 1) * 3, which is 9.
Show SolutionHide Solution
Approach
Read the tokens left to right with a stack of numbers. Push each number. When an operator arrives, its operands are the two most recent values, so pop them, apply the operator, and push the result. At the end, the one value left is the answer.
token stack 2 2 1 2 1 + 3 2 + 1 3 3 3 * 9 3 * 3
Order matters for - and /. The right operand was pushed last, so it comes off first: pop b, then a, and compute a - b. Division needs care too. Truncating toward zero is what integer division already does in Go, C# and Java. In TypeScript, / keeps the fraction, so wrap it in Math.trunc. In Python, // rounds down instead (-7 // 2 is -4), so use int(a / b).
Time O(n)Space O(n)
OPERATORS = {"+", "-", "*", "/"}
def eval_rpn(tokens: list[str]) -> int:
"""Evaluates an expression in Reverse Polish Notation, where each operator
comes after its two operands. Division truncates toward zero."""
stack: list[int] = []
for token in tokens:
if token in OPERATORS:
# The right operand was pushed last, so it comes off first.
b = stack.pop()
a = stack.pop()
stack.append(apply(a, b, token))
else:
stack.append(int(token))
return stack.pop() # A valid expression leaves exactly one value.
def apply(a: int, b: int, op: str) -> int:
if op == "+":
return a + b
if op == "-":
return a - b
if op == "*":
return a * b
return int(a / b) # Not a // b, which rounds down: -7 // 2 is -4, not -3.
if __name__ == "__main__":
print(eval_rpn(["2", "1", "+", "3", "*"])) # 9
print(eval_rpn(["4", "13", "5", "/", "+"])) # 6
print(eval_rpn(["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"])) # 22
print(eval_rpn(["-7", "2", "/"])) # -3const OPERATORS = new Set(["+", "-", "*", "/"]);
// Evaluates an expression in Reverse Polish Notation, where each operator
// comes after its two operands. Division truncates toward zero.
export function evalRPN(tokens: string[]): number {
const stack: number[] = [];
for (const token of tokens) {
if (OPERATORS.has(token)) {
// The right operand was pushed last, so it comes off first.
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(apply(a, b, token));
} else {
stack.push(Number(token));
}
}
return stack.pop()!; // A valid expression leaves exactly one value.
}
function apply(a: number, b: number, op: string): number {
switch (op) {
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
default:
return Math.trunc(a / b); // Not Math.floor, which rounds down: -3.5 would become -4.
}
}
console.log(evalRPN(["2", "1", "+", "3", "*"])); // 9
console.log(evalRPN(["4", "13", "5", "/", "+"])); // 6
console.log(evalRPN(["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"])); // 22
console.log(evalRPN(["-7", "2", "/"])); // -3package main
import (
"fmt"
"strconv"
)
var operators = map[string]bool{"+": true, "-": true, "*": true, "/": true}
// evalRPN evaluates an expression in Reverse Polish Notation, where each
// operator comes after its two operands. Division truncates toward zero.
func evalRPN(tokens []string) int {
stack := []int{}
for _, token := range tokens {
if operators[token] {
// The right operand was pushed last, so it comes off first.
b, a := stack[len(stack)-1], stack[len(stack)-2]
stack = stack[:len(stack)-2]
stack = append(stack, apply(a, b, token))
} else {
n, _ := strconv.Atoi(token) // Every other token is a valid integer.
stack = append(stack, n)
}
}
return stack[len(stack)-1] // A valid expression leaves exactly one value.
}
func apply(a, b int, op string) int {
switch op {
case "+":
return a + b
case "-":
return a - b
case "*":
return a * b
default:
return a / b // Integer division in Go already truncates toward zero.
}
}
func main() {
fmt.Println(evalRPN([]string{"2", "1", "+", "3", "*"})) // 9
fmt.Println(evalRPN([]string{"4", "13", "5", "/", "+"})) // 6
fmt.Println(evalRPN([]string{"10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"})) // 22
fmt.Println(evalRPN([]string{"-7", "2", "/"})) // -3
}using System;
using System.Collections.Generic;
public class Solution
{
static readonly HashSet<string> Operators = new() { "+", "-", "*", "/" };
// Evaluates an expression in Reverse Polish Notation, where each operator
// comes after its two operands. Division truncates toward zero.
public static int EvalRpn(string[] tokens)
{
var stack = new Stack<int>();
foreach (string token in tokens)
{
if (Operators.Contains(token))
{
// The right operand was pushed last, so it comes off first.
int b = stack.Pop();
int a = stack.Pop();
stack.Push(Apply(a, b, token));
}
else
{
stack.Push(int.Parse(token));
}
}
return stack.Pop(); // A valid expression leaves exactly one value.
}
static int Apply(int a, int b, string op) => op switch
{
"+" => a + b,
"-" => a - b,
"*" => a * b,
_ => a / b, // Integer division in C# already truncates toward zero.
};
public static void Main()
{
Console.WriteLine(EvalRpn(new[] { "2", "1", "+", "3", "*" })); // 9
Console.WriteLine(EvalRpn(new[] { "4", "13", "5", "/", "+" })); // 6
Console.WriteLine(EvalRpn(new[] { "10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+" })); // 22
Console.WriteLine(EvalRpn(new[] { "-7", "2", "/" })); // -3
}
}import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Set;
public class Solution {
static final Set<String> OPERATORS = Set.of("+", "-", "*", "/");
// Evaluates an expression in Reverse Polish Notation, where each operator
// comes after its two operands. Division truncates toward zero.
static int evalRPN(String[] tokens) {
Deque<Integer> stack = new ArrayDeque<>();
for (String token : tokens) {
if (OPERATORS.contains(token)) {
// The right operand was pushed last, so it comes off first.
int b = stack.pop();
int a = stack.pop();
stack.push(apply(a, b, token));
} else {
stack.push(Integer.parseInt(token));
}
}
return stack.pop(); // A valid expression leaves exactly one value.
}
static int apply(int a, int b, String op) {
return switch (op) {
case "+" -> a + b;
case "-" -> a - b;
case "*" -> a * b;
default -> a / b; // Integer division in Java already truncates toward zero.
};
}
public static void main(String[] args) {
System.out.println(evalRPN(new String[] {"2", "1", "+", "3", "*"})); // 9
System.out.println(evalRPN(new String[] {"4", "13", "5", "/", "+"})); // 6
System.out.println(evalRPN(new String[] {"10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"})); // 22
System.out.println(evalRPN(new String[] {"-7", "2", "/"})); // -3
}
}Common Pitfalls
- Popping or peeking an empty stack. Check first, as when a closing bracket arrives before any opener.
- Using
shift()orlist.pop(0)to dequeue. Each call is O(n), so a BFS over n nodes quietly becomes O(n²). - Popping operands in the wrong order. In
a b -,bcomes off the stack first, so computea - b, notb - a. - Forgetting to check that the stack is empty at the end.
((never fails a match, but it's still invalid. - Using Python's
//when a problem says to truncate toward zero.-7 // 2is-4, not-3.
Practice
More problems to try on your own.