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