backend / python core / tricky questions / 07_chained_comparisons.md

Chained comparisons

1 min read source

Chained comparisons

The gotcha

a < b < c is not (a < b) < c — it’s (a < b) and (b < c), with b evaluated only once.

Minimal repro

1 < 2 < 3        # True   — works as expected
3 > 2 > 1        # True
1 < 2 > 0        # True   — `2 < 1 and 2 > 0`? No: it's `1 < 2 and 2 > 0`
False == False == True   # False   — equivalent to `False == False and False == True`

# Side-effect surprise:
def b():
    print("called")
    return 5

1 < b() < 10     # b() called once, prints "called" once

Why it happens

Python’s grammar treats comparisons specially: any sequence of comparison operators < <= > >= == != chains with implicit and. The middle operand is evaluated once, so f() < x < g() calls f and g exactly once each, and short-circuits on the left.

This is unique to comparison operators. a + b + c is just left-associative; a < b < c is not (a < b) < c.

How to avoid

It’s mostly a feature, not a bug — 0 <= idx < len(arr) reads naturally. The trap is when you don’t realize the chaining is happening:

False == False == True
# Reads as "False equals False equals True" → expected True
# Actually: False == False AND False == True → True AND False → False

When in doubt, parenthesize: (False == False) == TrueTrue.

Interview angle

Predict the value of False == False == True or 1 < 2 == 2. Bonus question: “How many times is f() called in f() < g() < f()?” (Answer: twice — once for f() on the left, once for g() in the middle, then f() again on the right.)