How Python Evaluates Chained Comparisons

In Python, chained comparison operators like 1 < x < 10 provide a clean, mathematical syntax for evaluating multiple conditions simultaneously. Rather than evaluating expressions strictly from left to right as independent boolean operations, Python transforms chained comparisons into combined logical conditions using an implicit and. This article explains the exact evaluation model Python uses, including how intermediate operands are evaluated only once and how short-circuit evaluation applies.

The Logical Transformation

When Python encounters a chained comparison in the form:

a op1 b op2 c

it treats it as logically equivalent to:

(a op1 b) and (b op2 c)

In traditional programming languages like C, Java, or JavaScript, writing 1 < x < 10 causes the left expression 1 < x to resolve first to a boolean (True or False, or 1 or 0). That boolean is then compared to the next operand (evaluating True < 10 or False < 10), which produces unexpected bugs. Python avoids this by treating comparisons as chained operators at the grammar level.

Single Evaluation of Operands

While a < b < c behaves logically like (a < b) and (b < c), there is a critical distinction: Python guarantees that intermediate operands are evaluated only once.

Consider this example with a function that produces a side effect:

def get_number():
    print("Function called!")
    return 5

result = 1 < get_number() < 10

Output:

Function called!

If Python simply expanded the expression to 1 < get_number() and get_number() < 10, get_number() would execute twice. Instead, Python internally caches the result of the middle operand:

# Conceptual representation of internal execution
temp = get_number()
result = (1 < temp) and (temp < 10)

This ensures performance is optimized and prevents unintended bugs when evaluating complex expressions, database queries, or generators.

Short-Circuit Evaluation

Because chained comparisons expand with the semantics of the and operator, they inherit Python's short-circuit behavior. Evaluation stops immediately as soon as any comparison in the chain evaluates to False. Subsequent comparisons and expressions are skipped entirely.

def expensive_operation():
    print("This will not run.")
    return 15

# The first comparison (5 < 2) is False
result = 5 < 2 < expensive_operation()

In this case:

  1. 5 < 2 evaluates to False.
  2. Python short-circuits.
  3. expensive_operation() is never called, and result evaluates to False.

Chaining Across Different Comparison Operators

Chaining is not restricted to standard inequalities (<, <=, >, >=). Python allows any comparison operator to be chained together, including equality checks (==, !=), identity checks (is, is not), and membership tests (in, not in).

For example:

While syntactically valid, mixing completely different categories of operators (such as inequalities with membership tests) is often discouraged in production code because it can reduce readability for other developers.