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 listitem— a temporary variable that takes each value from the iterableiterable— any object you can loop over: list, tuple, string, range, generatorcondition(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 ofNonevalues. - Complex logic — if the comprehension needs multiple
if/elsebranches 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:
- List Slicing in Python — extract sublists with concise slice syntax
- Mastering the ‘in’ Operator — using
infor filtering inside comprehensions - The 5 Coolest Things About Using Python — why features like comprehensions make Python special