Skip to content
C Codeloom
Python

Python List Comprehensions Deep Dive

Go beyond the basics of Python list comprehensions — filtering, nested loops, conditional expressions, dict and set comprehensions, and when a plain for loop is the better choice.

·5 min read · By Codeloom
Beginner 10 min read

What you'll learn

  • The full syntax of list comprehensions
  • Filtering with if and the conditional expression
  • Nested comprehensions and when to avoid them
  • Dict and set comprehensions
  • When a regular for loop is clearer

Prerequisites

  • Basic Python familiarity

A list comprehension is Python’s shorthand for the pattern “build a list by transforming or filtering another iterable.” Once you understand the full syntax — including the parts that look strange at first — comprehensions become the natural way to express many small data transformations.

The Basic Form

A comprehension has three parts: an expression, a for clause, and an iterable.

squares = [x * x for x in range(5)]
print(squares)  # [0, 1, 4, 9, 16]

It is equivalent to:

squares = []
for x in range(5):
    squares.append(x * x)

The comprehension is shorter and, with practice, easier to read because the entire intent fits on one line.

Filtering with if

Add an if clause at the end to filter the input.

evens = [x for x in range(10) if x % 2 == 0]
print(evens)  # [0, 2, 4, 6, 8]

The if decides whether each element is included. You can stack multiple conditions.

result = [x for x in range(50) if x % 2 == 0 if x % 3 == 0]
# same as: if x % 2 == 0 and x % 3 == 0

Conditional Expressions in the Output

The trailing if filters elements. To transform elements differently based on a condition, use the conditional expression A if cond else B in the output part.

labels = ["even" if x % 2 == 0 else "odd" for x in range(5)]
print(labels)  # ['even', 'odd', 'even', 'odd', 'even']

The two forms look similar but live in different places: a filtering if goes after the for, while a transforming if/else goes in the expression at the start.

Combining Both

You can filter and transform in the same comprehension.

prices = [10, 20, 30, 40, 50]
discounted = [p * 0.9 if p > 25 else p for p in prices if p >= 20]
print(discounted)  # [20, 27.0, 36.0, 45.0]

Read it as: for each p in prices, if p >= 20, include either p * 0.9 or p depending on whether p > 25.

Nested Loops

A comprehension can have more than one for clause. They nest left to right, like nested loops.

pairs = [(x, y) for x in range(3) for y in range(3) if x != y]
print(pairs)

That is equivalent to:

pairs = []
for x in range(3):
    for y in range(3):
        if x != y:
            pairs.append((x, y))

Two for clauses are usually fine. Three start to hurt readability, and at that point a plain loop is often clearer.

Flattening a Nested List

A classic use of nested comprehensions is flattening.

nested = [[1, 2, 3], [4, 5], [6]]
flat = [x for row in nested for x in row]
print(flat)  # [1, 2, 3, 4, 5, 6]

The order of the for clauses matches the order of nested loops. Inner-most variable last.

Dict and Set Comprehensions

The same syntax extends to dicts and sets.

squares = {x: x * x for x in range(5)}
print(squares)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

unique_lengths = {len(word) for word in ["hi", "hello", "yo"]}
print(unique_lengths)  # {2, 5}

The dict form uses key: value and curly braces. The set form has just an expression in curly braces. (Curly braces with a plain expression and no colon produce a set; with a colon, a dict.)

Generator Expressions

Replace the square brackets with parentheses to get a generator expression — a lazy sequence that produces values on demand.

total = sum(x * x for x in range(1_000_000))

If you only need to iterate once and never index into the result, prefer a generator expression. It uses constant memory regardless of the size of the input.

When a Loop Is Better

Comprehensions are great for transformations that fit comfortably on one line. They become a problem when:

  • The expression has side effects (printing, mutating, calling APIs). A for loop makes the intent obvious; a comprehension hides it.
  • There are more than two for clauses or two if clauses. Readers stop being able to trace the order.
  • The line is so long that it wraps. At that point the visual benefit is gone.

Compare:

# Comprehension — fine
words = [w.lower() for w in raw if w.isalpha()]

# Hard to read at a glance
results = [
    transform(x, y, z)
    for x in xs
    for y in ys
    for z in zs
    if x != y and y != z and predicate(x, y)
]

The second one belongs as nested for loops, possibly factored into a helper function.

Performance

Comprehensions are slightly faster than the equivalent for loop with append, because they avoid repeated attribute lookups for append. Don’t pick them for speed alone, though — readability is the bigger win.

A Small Pattern: Group with a Comprehension

A neat trick for “group items by key” using a dict comprehension and set.

words = ["apple", "ant", "bee", "bear", "cat"]
buckets = {letter: [w for w in words if w.startswith(letter)] for letter in {w[0] for w in words}}
print(buckets)

You can absolutely use collections.defaultdict instead — and for anything more complex, you should — but the one-liner is a good demonstration of nesting comprehensions inside each other.

Wrapping Up

List comprehensions, generator expressions, and their dict and set cousins are some of Python’s most distinctive features. Master the full syntax, recognise where they make code clearer, and recognise where they don’t. Used well, they turn paragraphs of loops into single sentences that read like English.