Back to Blog
Python

Python List Comprehension Syntax Explained

python list comprehension syntax: Understand Python list comprehension syntax with practical examples, conditionals, nested loops, and performance tradeoffs.

list comprehensionpython syntaxgenerator expressionsnested loopscode readability
Illustration of Python list comprehension syntax with a list and transformation arrow

The Python list comprehension syntax lets you build a new list from an existing iterable in a single line. It replaces the common pattern of initializing an empty list, looping over a source, and appending each transformed item. The syntax is compact, but it also carries a few subtleties that can trip up developers who are new to it or who only use it occasionally.

Basic Syntax and How It Maps to a for Loop

The simplest form of a list comprehension is:

new_list = [expression for item in iterable]

The expression is evaluated for each item in the iterable, and the results are collected into a new list. This is equivalent to:

new_list = [] for item in iterable: new_list.append(expression)

For example, to square every number in a range:

squares = [x ** 2 for x in range(10)]

This produces [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]. The comprehension reads almost like a sentence: "for each x in range(10), compute x squared." That readability is the main reason to prefer it over an explicit loop when the logic is simple.

The expression can be any Python expression, including function calls, attribute access, or arithmetic. It does not have to use the loop variable, though it usually does. If the expression has side effects, those side effects occur once per iteration, exactly as they would in a loop.

Adding a Condition with if

You can filter items by appending an if clause after the for clause:

new_list = [expression for item in iterable if condition]

The condition is evaluated for each item. Only items for which the condition is truthy are passed to the expression. The equivalent loop is:

new_list = [] for item in iterable: if condition: new_list.append(expression)

A common use is extracting even numbers:

evens = [x for x in range(20) if x % 2 == 0]

The if clause can also use the item in more complex predicates, such as checking membership or calling a function. For instance, to keep only non-empty strings from a list:

words = ["", "alpha", "beta", "", "gamma"] non_empty = [w for w in words if w]

Because an empty string is falsy, this filters it out. The condition is evaluated before the expression, so you can safely reference the item in both.

Using Multiple for Clauses for Nested Loops

List comprehensions can contain multiple for clauses, which behave like nested loops. The leftmost for is the outer loop, and each subsequent for is nested inside it. The general form is:

new_list = [expression for outer_item in outer_iterable for inner_item in inner_iterable]

For example, to flatten a matrix:

matrix = [[1, 2], [3, 4], [5, 6]] flat = [num for row in matrix for num in row]

The result is [1, 2, 3, 4, 5, 6]. The order of the for clauses matters: it matches the order you would write nested loops. The equivalent loop is:

flat = [] for row in matrix: for num in row: flat.append(num)

You can combine multiple for clauses with an if condition. The condition is evaluated at the deepest level, after all for clauses have bound their variables. For example, to get all pairs (a, b) where a is even and b is odd:

pairs = [(a, b) for a in range(5) for b in range(5) if a % 2 == 0 and b % 2 == 1]

This is equivalent to:

pairs = [] for a in range(5): for b in range(5): if a % 2 == 0 and b % 2 == 1: pairs.append((a, b))

When you have more than two for clauses, the comprehension becomes harder to read. In that case, a regular nested loop or a helper function is usually clearer.

Transforming Elements with Expressions

The expression in a list comprehension is not limited to simple arithmetic. It can call methods, access dictionary keys, or invoke functions. For example, to normalize a list of strings to lowercase:

names = ["Alice", "BOB", "Carol"] lowered = [name.lower() for name in names]

You can also use a conditional expression (if/else) inside the expression itself, which is different from the if filter. This allows you to transform each item in one of two ways:

values = [1, -2, 3, -4] labeled = ["positive" if v > 0 else "negative" for v in values]

The result is ["positive", "negative", "positive", "negative"]. The conditional expression is evaluated for every item, so the list length stays the same. This is a common source of confusion: the if at the end filters, while the if inside the expression transforms.

When the expression becomes long or involves complex logic, the comprehension loses its readability advantage. In that case, extract the logic into a named function and call it from the comprehension:

def classify(v): if v > 0: return "positive" if v < 0: return "negative" return "zero" result = [classify(v) for v in values]

This keeps the comprehension short and the logic testable.

List Comprehensions vs. Generator Expressions

A list comprehension builds the entire list in memory at once. If the source iterable is large or the transformation is expensive, that can be wasteful. A generator expression uses the same syntax but with parentheses instead of brackets, and it yields items lazily:

gen = (x ** 2 for x in range(10))

A generator expression does not produce a list. It returns an iterator that computes each value on demand. You can iterate over it once, or pass it to a function like sum() or max() without materializing the full list:

total = sum(x ** 2 for x in range(10))

This is often more memory-efficient because only one item exists at a time. However, you cannot index into a generator or reuse it after it is exhausted. If you need random access or need to iterate multiple times, a list comprehension is the right choice.

The decision between the two comes down to how you consume the data. If you only need to iterate once and the source is large, use a generator expression. If you need a concrete list for later indexing, slicing, or repeated iteration, use a list comprehension.

Common Mistakes and Readability Concerns

One frequent mistake is using a list comprehension for its side effects rather than its result. For example, calling print() or mutating an external variable inside the expression works, but it obscures intent and makes the code harder to read. A regular for loop is clearer for side-effect-driven code.

Another mistake is misplacing the if filter relative to the expression. Remember that the if at the end filters the source items, while an if inside the expression chooses between two output values. Mixing them can produce surprising results.

Nested comprehensions can become difficult to read when they span multiple lines. Python allows line breaks inside brackets, so you can format a nested comprehension for clarity:

pairs = [ (a, b) for a in range(10) for b in range(10) if a != b ]

This is still a single comprehension, but the line breaks make the structure visible. If the comprehension gets longer than a few lines, consider refactoring into a loop or a generator function.

When Not to Use a List Comprehension

List comprehensions are not always the best tool. If the transformation logic is complex, a named function or a generator function with yield often reads better. If you need to handle exceptions during iteration, a comprehension has no built-in way to catch them; you would need to wrap the expression in a function that handles errors, or use a regular loop with a try/except block.

Comprehensions also cannot easily break out of the loop early. If you need to stop iterating once a condition is met, a for loop with break is the appropriate structure. For example, finding the first even number in a large list is better done with a loop than with a comprehension that would process every item.

Finally, consider the readability of your team. A comprehension with multiple for clauses and a filter may be compact, but if your colleagues are not familiar with the syntax, it can slow down code review. The goal is to write code that is obvious at first glance. When a comprehension becomes cryptic, a simple loop is often the more maintainable choice.

Performance is rarely the deciding factor. List comprehensions are faster than an explicit for loop with append() because the loop runs in C rather than in Python bytecode, but the difference is usually small for typical data sizes. Generator expressions add a small overhead per iteration but save memory. The real benefit of comprehensions is clarity, not speed. If you are working with very large datasets, measure the actual memory and time behavior rather than assuming a comprehension is always better.

python list comprehension syntax: Practical Usage and Code E | RYUSLOG DEV