Back to Blog
Python

Python Chained Comparison: Syntax and Behavior

python chained comparison: Learn how Python's chained comparisons work, including evaluation order, short-circuiting, and practical use cases for cleaner conditionals.

chained comparisonpython syntaxcomparison operatorsshort-circuit evaluationcode readability
Diagram showing a chained comparison expression in Python with three values and two comparison operators.

python chained comparison requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

Python's chained comparison syntax lets you write a < b < c to test whether b is between a and c. This is not just a syntactic shortcut; it has specific evaluation semantics that differ from a naive expansion. In this article, we'll look at how chained comparisons work, where they are useful, and where they can surprise you.

How Chained Comparisons Are Evaluated

When you write a < b < c, Python evaluates it as a < b and b < c. The crucial difference from writing that explicit and expression is that the middle operand b is evaluated only once. Consider this example:

def get_value(): print("get_value called") return 5 if 0 < get_value() < 10: print("in range")

Here, get_value() is called exactly once, not twice. If you expanded it manually as 0 < get_value() and get_value() < 10, the function would run twice, which can be a problem if it has side effects or is expensive.

Chained comparisons also short-circuit. If the first comparison is false, the second one is not evaluated. In a < b < c, if a < b is false, b < c is never checked. This behavior is identical to the and operator.

Practical Use Cases for Chained Comparisons

The most common use is range checking. Instead of writing if value >= 0 and value <= 100:, you can write:

if 0 <= value <= 100: print("valid score")

This is more readable and directly expresses the intent: value is between 0 and 100. Chained comparisons also work with other operators. For example, you can check that three values are in increasing order:

if x < y < z: print("strictly increasing")

You can also mix operators, though it's rarely necessary. The syntax supports any combination of comparison operators, including ==, !=, in, is, and not in. For instance, a == b == c checks that all three are equal.

Common Mistakes and Edge Cases

One subtle pitfall is that chaining changes the meaning when you use in or is. For example, a in b in c means a in b and b in c, not (a in b) in c. This can lead to unexpected behavior if you're not careful. Consider:

value = 2 list1 = [1, 2, 3] list2 = [2, 3, 4] print(value in list1 in list2) # False, because list1 is not in list2

The expression evaluates value in list1 (True) and then list1 in list2 (False), so the result is False. If you meant to check membership in both lists, you'd need value in list1 and value in list2.

Another edge case is side effects in the middle operand. Since it's evaluated only once, you get a consistent value for both comparisons. But if you use a variable that changes during evaluation, the result may differ from an explicit and version. In practice, this is rarely an issue because you should avoid side effects in conditions.

Performance and Runtime Behavior

Chained comparisons can be more efficient than an explicit and when the middle operand is an expensive expression. Because it's evaluated once, you avoid redundant work. Short-circuiting also prevents unnecessary evaluations when the first comparison fails. There is no additional function call overhead; the comparison operators are applied directly.

This performance benefit is usually negligible in typical code, but it matters in hot loops or when the middle expression involves a database query or a complex calculation. The readability gain is often more valuable than the micro-optimization.

Chained Comparisons vs. Explicit and

Both forms have their place. Chained comparisons are ideal when the comparisons are logically connected, such as a range check or an ordering test. They make the code more concise and align with how you'd read the condition aloud: "x is less than y which is less than z."

Explicit and is clearer when the conditions are independent. For example, if user.is_active and user.role == 'admin': is better than trying to chain unrelated checks. Chaining unrelated comparisons would force a connection that doesn't exist and hurt readability.

A good rule of thumb: use a chained comparison when the middle operand is the subject of both comparisons. Otherwise, use and.

Style and Compatibility

Chained comparisons are supported in all Python versions, including Python 2 and Python 3. They are considered idiomatic Python and are not discouraged by PEP 8. However, extremely long chains can become hard to read. For instance, a < b < c < d < e is valid but may be better split across lines or rewritten with all() for clarity.

Some linters may flag chains longer than three or four operands. If you encounter that, consider using all() with a generator:

if all(x[i] < x[i + 1] for i in range(len(x) - 1)): print("sorted")

This is more explicit and scales better for large sequences. The chained comparison remains the right tool for short, fixed-length checks.

When writing new code, prefer chained comparisons for simple range and ordering checks. They reduce visual noise and make the condition's intent obvious. Just be mindful of the semantics when mixing operators, and keep chains short enough to read at a glance.

python chained comparison: Practical Usage and Code Examples | RYUSLOG DEV