Comparison Chaining
Write a < b < c like real mathematics — and understand exactly how Python evaluates it.
Most languages force you to write a < b and b < c. Python lets you chain comparisons so the code reads like the maths on paper: a < b < c.
How it actually works
Python rewrites a < b < c as (a < b) and (b < c), with one important twist: the middle expression b is evaluated only once. That matters when b is an expensive function call or has a side effect.
x = 5
print(1 < x < 10) # True
print(10 < x < 20) # False
# You can mix operators, too:
print(0 <= x < 10) # True (in range [0, 10))The gotcha to know about
Chaining is and-based, so a == b == c tests that all three are equal — great. But a < b > c is legal and rarely what you mean; keep chains monotonic (all in the same direction) for readability.
Example
calls = []
def next_value():
"""A stand-in for an expensive or side-effecting call."""
calls.append(1)
return 7
# The middle operand is evaluated exactly ONCE
result = 0 <= next_value() < 10
print('result :', result) # True
print('times called :', len(calls)) # 1, not 2
# A clean 'is this a valid grade?' check
def grade_letter(score):
if not 0 <= score <= 100:
raise ValueError(f'score {score} out of range')
if 90 <= score <= 100: return 'A'
elif 80 <= score < 90: return 'B'
elif 70 <= score < 80: return 'C'
else: return 'F'
for s in (95, 83, 72, 40):
print(s, '->', grade_letter(s))
# Output:
# result : True
# times called : 1
# 95 -> A
# 83 -> B
# 72 -> C
# 40 -> FWhen to use it
- A bounds checker uses 0 <= index < length to validate an array index in one readable expression.
- A date validator writes start <= event_date <= end to confirm a date falls within a window.
- A grading function uses 70 <= score < 80 inside a chained comparison to assign the B grade band.
More examples
Chained range check
Chains two comparisons so age is tested against both bounds in a single, readable expression.
age = 25
if 18 <= age < 65:
print('Working age adult')
# Equivalent (but longer):
if age >= 18 and age < 65:
print('Working age adult')Chaining evaluates each pair once
Illustrates that chained comparisons test each adjacent pair, unlike parenthesised forms that compare booleans.
a, b, c = 1, 2, 3
result = a < b < c # True: 1<2 AND 2<3
print(result)
# Contrast: non-chained version
result2 = (a < b) < c # (True) < 3 -> 1 < 3 -> True (but different!)
print(result2)Multi-step strict chain
Shows a strict inequality chain and a mixed-type chain with int/float equality.
x = 5
print(1 < x < 10) # True
print(1 < x < 5) # False (x is not strictly less than 5)
print(x == 5 == 5.0) # True (chaining works across types)
Discussion