Simple Operator Precedence Examples in Python: Complete Expression Evaluation and Order of Operations Guide

Simple Operator Precedence Examples in python

When I first started writing Python, I assumed the interpreter would evaluate expressions the same way I read them — left to right, plain and simple. It didn’t take long before a line like 2 + 3 * 4 gave me 14 instead of 20, and I realized I needed to actually understand operator precedence instead of guessing. In this guide, I’m going to walk through everything I’ve learned about how Python decides what to compute first, why it matters, and how to avoid the subtle bugs that come from ignoring it.

What Operator Precedence Actually Means

Operator precedence is the set of rules Python uses to decide which operator gets evaluated first when an expression contains more than one. It’s the same idea as the “order of operations” I learned in school math class (PEMDAS/BODMAS), except Python extends it to cover comparisons, boolean logic, bitwise operations, and more.

Consider this:

result = 2 + 3 * 4
print(result)  # Output: 14

Multiplication has higher precedence than addition, so Python computes 3 * 4 first, then adds 2. If I wanted the addition to happen first, I’d need parentheses:

result = (2 + 3) * 4
print(result)  # Output: 20

That’s the core idea. Everything else in this article is really just an extension of that one principle: operators with higher precedence bind tighter than operators with lower precedence.

Why This Matters Beyond Simple Arithmetic

I used to think precedence only mattered for math homework-style expressions. Then I ran into bugs like this one:

x = 5
y = 10
if x > 0 and y > 0 or x > 100:
    print("condition met")

Understanding that and binds tighter than or (more on this below) changed how I read that line entirely. If I hadn’t understood precedence, I would have misjudged which conditions were actually being grouped together.

Python’s Full Precedence Table (High to Low)

Here’s the order I keep bookmarked, from highest precedence (evaluated first) to lowest (evaluated last):

  1. () — Parentheses (grouping)
  2. ** — Exponentiation
  3. +x, -x, ~x — Unary plus, minus, bitwise NOT
  4. *, /, //, % — Multiplication, division, floor division, modulus
  5. +, - — Addition, subtraction
  6. <<, >> — Bitwise shifts
  7. & — Bitwise AND
  8. ^ — Bitwise XOR
  9. | — Bitwise OR
  10. Comparisons — ==, !=, <, >, <=, >=, is, is not, in, not in
  11. not
  12. and
  13. or

A quirk worth remembering: exponentiation (**) is right-associative, and unlike most binary operators, it binds tighter than unary minus on its left side.

print(-2 ** 2)   # Output: -4, because it's -(2**2), not (-2)**2
print(2 ** 3 ** 2)  # Output: 512, because it's 2 ** (3 ** 2), right-to-left

That second example trips up a lot of people, myself included, the first time I saw it.

Associativity: The Other Half of the Story

Precedence tells Python which operator goes first when operators differ. Associativity tells Python what to do when operators of the same precedence appear together.

Most operators in Python are left-associative, meaning they evaluate left to right:

print(10 - 3 - 2)  # Output: 5, because (10 - 3) - 2

Exponentiation is the main exception, being right-associative:

print(2 ** 2 ** 3)  # Output: 256, because 2 ** (2 ** 3)

Simple Arithmetic Precedence Examples

print(10 + 2 * 5)      # 20 -> multiplication first
print((10 + 2) * 5)    # 60 -> parentheses override
print(20 / 4 - 2)      # 3.0 -> division first
print(20 / (4 - 2))    # 10.0 -> subtraction first
print(2 + 3 * 4 - 1)   # 13 -> multiplication first, then left-to-right add/sub
print(10 % 3 + 1)      # 2 -> modulus first

Comparison and Boolean Precedence

This is where I’ve seen the most real-world bugs, especially in conditionals with mixed logic.

a = 5
b = 10
c = 15

print(a < b and b < c)        # True
print(a < b and b < c or a > c)  # True, 'and' binds tighter than 'or'
print(not a > b)              # True, since a > b is False

Because not has higher precedence than and, and and has higher precedence than or, an expression like:

x = True or False and False

evaluates as True or (False and False), which is True — not (True or False) and False, which would be False. I always add parentheses in situations like this now, purely for readability, even when I know the precedence rules cold.

Bitwise Operator Precedence

Bitwise operators have lower precedence than arithmetic operators, which surprises people coming from languages with different rules.

print(5 & 3 + 2)   # 5 + 2 = 7, then 5 & 7 -> 5
print((5 & 3) + 2) # 5 & 3 = 1, then 1 + 2 -> 3

I always wrap bitwise expressions in parentheses when they’re mixed with arithmetic — it costs nothing and saves a debugging session.

How Python Evaluates Expressions Internally

Under the hood, Python’s parser builds an Abstract Syntax Tree (AST) from source code before execution. Operator precedence and associativity are encoded directly into the grammar rules used to build this tree. Higher-precedence operators end up deeper in the tree (evaluated first), because the parser groups tighter-binding operations into subexpressions before combining them with looser-binding ones.

I can actually see this for myself using Python’s built-in ast module:

import ast

tree = ast.parse("2 + 3 * 4", mode="eval")
print(ast.dump(tree))

Output (simplified):

Expression(body=BinOp(left=Constant(value=2), op=Add(),
right=BinOp(left=Constant(value=3), op=Mult(), right=Constant(value=4))))

Notice how the multiplication (3 * 4) is nested as the right-hand operand of the addition — that nesting is precedence made visible.

Performance Considerations

Operator precedence itself doesn’t have a meaningful performance cost — evaluation order is resolved at parse time, not runtime. However, how I structure expressions can matter:

  • Adding unnecessary parentheses has zero runtime cost in CPython since they only affect parsing, not the compiled bytecode.
  • Short-circuit evaluation in and/or can meaningfully affect performance and behavior — Python stops evaluating as soon as the result is determined.
def expensive_check():
    print("Called!")
    return True

# Short-circuiting means expensive_check() never runs here
result = False and expensive_check()
print(result)  # Output: False (no "Called!" printed)

I use this short-circuit behavior deliberately in real code — putting cheap, likely-to-fail conditions first in an and chain to avoid unnecessary function calls.

Real-World and Automation Use Cases

In scripts I’ve written for data validation, precedence awareness has saved me more than once. For example, filtering records:

records = [
    {"age": 25, "active": True},
    {"age": 17, "active": True},
    {"age": 30, "active": False},
]

valid = [r for r in records if r["age"] >= 18 and r["active"]]
print(valid)

Without understanding that and binds the two conditions together tightly (and that comparisons happen before and), it would be easy to misread more complex filter conditions in automation pipelines, config validators, or ETL scripts.

Best Practices I Follow

  • Use parentheses liberally for clarity, even when they’re not strictly required. Code is read far more often than it’s written.
  • Don’t rely on memorized precedence for complex boolean logic — break it into named intermediate variables instead.
  • Be extra careful with bitwise operators mixed with comparisons or arithmetic — this is the single most common precedence mistake I’ve seen in real code.
  • Test edge cases like -2 ** 2 and chained comparisons (1 < x < 10, which is valid and intuitive in Python, unlike many other languages).

Common Mistakes

One mistake I made early on was assuming chained comparisons worked like in C:

x = 5
print(1 < x < 10)  # True — this is (1 < x) and (x < 10)

This is actually a Python feature, not a precedence trap, but it confuses people moving from other languages who expect 1 < x to evaluate first and then get compared against 10.

Another common mistake:

print(not 1 == 2)   # True: not (1 == 2)
print((not 1) == 2) # False: (not 1) is False, False == 2 is False

not has lower precedence than ==, so not 1 == 2 parses as not (1 == 2), which surprises people who think not applies only to the immediate value next to it.

Debugging Tips

When I suspect a precedence issue is causing a bug:

  1. Add explicit parentheses around each sub-expression and re-run.
  2. Use ast.dump() as shown above to see exactly how Python parsed the expression.
  3. Break the expression into separate variables with descriptive names and print each one.
condition1 = a > 0
condition2 = b > 0
condition3 = a > 100
final = (condition1 and condition2) or condition3
print(final)

FAQs

Does Python evaluate expressions strictly left to right? No. Left-to-right is the associativity rule for most operators, but precedence determines which operator is applied first when different operators are mixed.

Is ** left-associative or right-associative? Right-associative — 2 ** 3 ** 2 evaluates as 2 ** (3 ** 2).

Do parentheses ever slow down my code? No, in CPython parentheses only guide parsing; they don’t add runtime overhead.

Why does -2 ** 2 return -4 instead of 4? Because ** binds tighter than unary minus, so it’s parsed as -(2 ** 2).

Summary

Operator precedence in Python isn’t just trivia — it’s the backbone of how every expression in my code actually gets evaluated. Once I internalized the order (parentheses, exponents, unary operators, multiplicative, additive, bitwise shifts, bitwise AND/XOR/OR, comparisons, not, and, or), reading and debugging code became far more predictable. When in doubt, I still reach for parentheses — they cost nothing and remove all ambiguity, both for Python and for whoever reads my code next.

References

Total
1
Shares

Leave a Reply

Previous Post
Boolean Operators in python

Boolean Operators in Python: Complete and, or, not Logical Operations and Truth Value Testing Guide

Next Post
Variable Scope and Binding in python

Variable Scope and Binding in Python: Complete Global, Local, and Nonlocal Variable Access Guide

Related Posts