List comprehensions are one of Python’s most distinctive features — a concise, readable syntax for creating lists from existing iterables. They replace verbose for loops with a single expressive line, and they’re faster too, because the iteration happens in C under the hood rather than through the Python bytecode interpreter.

Basic Syntax

[expression for item in iterable if condition]
  • expression — the value to include in the new list
  • item — a temporary variable that takes each value from the iterable
  • iterable — any object you can loop over: list, tuple, string, range, generator
  • condition (optional) — a filter that includes only items that pass the test

Simple Examples

Squares of even numbers:

squares = [x**2 for x in range(1, 11) if x % 2 == 0]
print(squares)
# Output: [4, 16, 36, 64, 100]

Extracting uppercase words:

words = ["Hello", "world", "PYTHON", "is", "GREAT"]
upper = [w for w in words if w.isupper()]
print(upper)
# Output: ['PYTHON', 'GREAT']

Flattening a list of lists:

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

Comprehension vs. Loop

The traditional approach:

result = []
for x in range(10):
    if x % 2 == 0:
        result.append(x ** 2)

The comprehension:

result = [x ** 2 for x in range(10) if x % 2 == 0]

Same result, less code, and roughly 10-30% faster for typical workloads because append lookups and function call overhead are eliminated.

Dictionary and Set Comprehensions

Comprehensions aren’t limited to lists. The same syntax works for dicts and sets.

Dictionary comprehension — double the values:

nums = {'a': 1, 'b': 2, 'c': 3}
doubled = {k: v * 2 for k, v in nums.items()}
print(doubled)
# Output: {'a': 2, 'b': 4, 'c': 6}

Inverting a dictionary:

original = {'a': 1, 'b': 2, 'c': 3}
inverted = {v: k for k, v in original.items()}
print(inverted)
# Output: {1: 'a', 2: 'b', 3: 'c'}

Set comprehension — unique tripled values:

nums = [1, 2, 2, 3, 3, 3]
tripled = {x * 3 for x in nums}
print(tripled)
# Output: {3, 6, 9}

Nested Comprehensions

You can nest comprehensions, but readability drops quickly. A good rule: if you need more than two for clauses, use a regular loop.

# Generating a multiplication table
table = [[row * col for col in range(1, 6)] for row in range(1, 6)]
for row in table:
    print(row)
# [1, 2, 3, 4, 5]
# [2, 4, 6, 8, 10]
# [3, 6, 9, 12, 15]
# [4, 8, 12, 16, 20]
# [5, 10, 15, 20, 25]

The Walrus Operator (Python 3.8+)

The := walrus operator lets you compute a value once and reuse it within the same comprehension — avoiding redundant function calls:

import math

values = [1, 4, 9, 16, 25, 36]
roots = [(n, r) for n in values if (r := math.sqrt(n)) == int(r)]
print(roots)
# Output: [(1, 1.0), (4, 2.0), (9, 3.0), (16, 4.0), (25, 5.0), (36, 6.0)]

Without :=, you’d call math.sqrt(n) twice — once in the if and once in the expression.

Generator Expressions

Replace the brackets with parentheses to get a generator expression — same syntax, but it yields items lazily instead of building the entire list in memory:

# List comprehension: builds the full list in memory
total = sum([x ** 2 for x in range(1_000_000)])

# Generator expression: processes one item at a time
total = sum(x ** 2 for x in range(1_000_000))

The generator version uses constant memory regardless of the range size. Use generators when you only need to iterate once and the dataset is large.

When Not to Use Comprehensions

Comprehensions are great, but don’t force them:

  • Side effects — if you’re calling a function for its side effect (printing, writing to a file), use a loop. [print(x) for x in items] is an anti-pattern that creates a throwaway list of None values.
  • Complex logic — if the comprehension needs multiple if/else branches or exceeds ~80 characters, a loop is clearer.
  • Readability — if a colleague would need to pause and decode it, it’s too clever.

Performance

Comprehensions are consistently faster than equivalent for loops with append:

import timeit

# List comprehension
timeit.timeit('[x**2 for x in range(1000)]', number=10000)
# ~1.8 seconds

# For loop with append
timeit.timeit('''
result = []
for x in range(1000):
    result.append(x**2)
''', number=10000)
# ~2.4 seconds

The difference comes from eliminating the per-iteration list.append method lookup and call. For larger datasets, the gap widens.


See also: