Order of Operations: The Deep Dive

This post builds on the intro post. If you have not read that one yet, start there first.

New to this topic? Start with: Order of Operations: What Runs First and Why It Matters


Associativity

Operator precedence tells you which operator runs first. Associativity tells you which direction to evaluate when two operators have the same priority.

Most operators in Python are left-to-right. Subtraction is a good example of why that matters:

10 - 3 - 2

Left-to-right gives you (10 - 3) - 2 = 5. Right-to-left would give you 10 - (3 - 2) = 9. Python uses left-to-right, so the answer is 5.

The one exception that surprises people is the exponent operator, which is right-to-left:

2 ** 3 ** 2

This evaluates as 2 ** (3 ** 2), which is 2 ** 9 = 512. Not (2 ** 3) ** 2 = 64. If you expected 64, you need parentheses.


Short-Circuit Evaluation

and and or do not just have a place in the precedence table. They also stop evaluating as soon as the result is certain.

With and, if the first condition is False, Python never checks the second one:

def slow_check():
    print("running slow check")
    return True

if False and slow_check():
    pass
# "running slow check" never prints

With or, if the first condition is True, Python never checks the second:

if True or slow_check():
    pass
# "running slow check" never prints

This is useful for protecting against errors:

items = []

# Safe - if items is empty, items[0] never runs
if items and items[0] == "admin":
    print("admin found")

Without the short-circuit, items[0] on an empty list would raise an IndexError.


Integer Division and Type Mixing

Python 3 has two division operators:

10 / 3    # 3.3333...  float division
10 // 3   # 3          integer division, rounds toward negative infinity
10 % 3    # 1          remainder

Modulo has some non-obvious behavior with negative numbers that is worth knowing, especially if you have a C or JavaScript background. A dedicated post on modulo is coming soon.

When you mix integers and floats, the result is always a float:

5 + 2.0   # 7.0
10 // 3.0 # 3.0  still integer division, but returns a float

A common bug with percentages:

score = 450
total = 1000

# Wrong in Python 2, correct in Python 3
percent = score / total * 100   # 45.0

# If you want an integer result
percent = score * 100 // total  # 45

Order matters here. score / total gives 0.45 first, then multiplied by 100 gives 45.0. If you wrote score / (total * 100) you would get 0.0045.


Chained Comparisons

Python lets you chain comparisons in a way most languages do not:

x = 5
1 < x < 10   # True

Python reads this as (1 < x) and (x < 10). In JavaScript or C# you would have to write that out explicitly:

x > 1 && x < 10

Chained comparisons work with any mix of operators:

0 <= score <= 100   # checks both bounds at once
a < b < c < d       # checks all three in sequence

Bitwise Operators

Bitwise operators work directly on the binary representation of integers. They come up regularly in game development, systems programming, and permission flags.

5 & 3    # 1   AND - bits that are set in both
5 | 3    # 7   OR  - bits that are set in either
5 ^ 3    # 6   XOR - bits that are set in one but not both
~5       # -6  NOT - flips all bits
5 << 1   # 10  left shift - multiply by 2
5 >> 1   # 2   right shift - divide by 2

Bitwise operators sit between arithmetic and comparison in the precedence table, which leads to this common mistake:

x = 5
if x & 3 == 1:    # reads as x & (3 == 1), which is x & False, which is 0
    print("match")

The fix:

if (x & 3) == 1:
    print("match")

A Game Development Example

Score calculation in a game:

kills = 5
kill_points = 10
headshot_bonus = 25
multiplier = 2

# Intent: (kills * kill_points + headshot_bonus) * multiplier
score = kills * kill_points + headshot_bonus * multiplier
# Actual: 50 + 50 = 100

score = (kills * kill_points + headshot_bonus) * multiplier
# Correct: (50 + 25) * 2 = 150

The first version compiles and runs without any error. It just gives you the wrong number. These are the hardest bugs to find because nothing breaks.


Language Differences

Feature Python JavaScript C#
Exponent ** ** Math.Pow()
Integer division // Math.floor(a/b) (int)(a/b)
Chained comparisons 1 < x < 10 not supported not supported
Strict equality == (always strict) === ==
Bitwise NOT ~x ~x ~x

JavaScript == performs type coercion, which is why 0 == false returns true there. Python == does not coerce types that way.


Excel: Nested Functions

When you nest functions in Excel, the innermost function runs first, then works outward:

=ROUND(SUM(A1:A5) / COUNT(A1:A5), 2)

Order: SUM runs, COUNT runs, division happens, then ROUND applies. Standard order of operations applies inside the arguments too:

=SUM(A1*2, B1+C1)   # multiplies A1 by 2, adds B1 and C1, then sums both

References


Ran into a case not covered here? Post it below and we will work through it.


New to this topic? Start with: Order of Operations: What Runs First and Why It Matters