Python List Comprehensions: The Complete Guide
List comprehensions are Python’s most elegant feature. They create lists in a single line where you’d normally write a for loop. More than just syntactic sugar, list comprehensions often execute faster than equivalent for-loop code because the iteration is performed at the C level inside the Python interpreter rather than through Python’s bytecode evaluation loop.
Beyond lists, Python extends the same syntax to dictionaries (dictionary comprehensions), sets (set comprehensions), and generator expressions. Mastering comprehensions will make your Python code more concise, more readable, and often more performant.
Basic Syntax
The anatomy of a list comprehension is simple: an expression followed by a for clause, with an optional if filter at the end.
new_list = [expression for item in iterable if condition]Read it as: “create a list by evaluating expression for each item in iterable where condition is true.” The expression can be any valid Python expression, including function calls, arithmetic, or even nested comprehensions.
Simple Examples
Start with straightforward transformations and filters:
# Squares of numbers 0-9
squares = [x**2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# Even numbers only
evens = [x for x in range(20) if x % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# String lengths
words = ["hello", "world", "python"]
lengths = [len(w) for w in words]
# [5, 5, 6]These examples show the three most common comprehension patterns: transforming every element (squares), filtering (evens), and mapping a function over elements (string lengths). Most real-world comprehensions are combinations of these three patterns.
vs Traditional Loop
The clearest way to appreciate comprehensions is to compare them side by side with traditional for-loops:
# Traditional
squares = []
for x in range(10):
squares.append(x**2)
# List comprehension (same result, 1 line)
squares = [x**2 for x in range(10)]The comprehension version eliminates three lines of boilerplate — the empty list initialization, the append call, and the loop body indentation. It also makes the intent immediately obvious: “I am creating a list of squares.” In the loop version, you must read all four lines to understand the result.
Performance Comparison
List comprehensions are typically 20–50% faster than manual for-loop appends for small to medium datasets. For large datasets (millions of items), the gap widens because the comprehension reduces Python-level bytecode execution. You can verify this with the timeit module:
import timeit
# Loop: 2.45 seconds
timeit.timeit('squares = []; [squares.append(x**2) for x in range(1000000)]', number=10)
# Comprehension: 1.85 seconds
timeit.timeit('[x**2 for x in range(1000000)]', number=10)With Conditionals
Conditionals can appear in two positions, each with different semantics.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Simple condition (filter — placed after the for)
evens = [n for n in numbers if n % 2 == 0]
# If-else (ternary expression — put condition before the for)
labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
# ['odd', 'even', 'odd', 'even', ...]The distinction is crucial: the trailing if filters items (some items are excluded entirely), while the if-else transforms every item (the list length stays the same). Mixing them lets you build sophisticated pipelines:
# Filter AND transform
[n * 10 if n > 5 else n for n in numbers if n != 3]This reads as: “for each number not equal to 3, multiply by 10 if greater than 5, otherwise keep the number.” The two conditions serve different purposes — one filters, one transforms.
Nested Loops
Equivalent to a nested for-loop, flattening a 2D structure or computing a Cartesian product:
# Flatten a matrix
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [num for row in matrix for num in row]
# [1, 2, 3, 4, 5, 6]
# Cartesian product
colors = ["red", "blue"]
sizes = ["S", "M", "L"]
combinations = [(c, s) for c in colors for s in sizes]
# [('red', 'S'), ('red', 'M'), ...]The order of the for clauses matches the order of a nested loop: the outer loop comes first. Read flat as: “for each row in the matrix, then for each number in that row, collect the number.” If you reverse the order, you get a different result.
Nested List Comprehensions
A comprehension inside another comprehension creates a matrix-like result:
# Matrix transpose
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
transpose = [[row[i] for row in matrix] for i in range(3)]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]Read the outer comprehension: “for each column index i, create a list containing the i-th element from every row.” Nested comprehensions are powerful but can hurt readability. If a nested comprehension is more than one line, consider using a regular loop or breaking it into steps with intermediate variables.
Dictionary and Set Comprehensions
The same syntax works for dictionaries (curly braces with key:value pairs) and sets (curly braces with single values).
# Dictionary comprehension
squares_dict = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# Set comprehension
unique_lengths = {len(w) for w in ["hi", "hello", "hey", "hi"]}
# {2, 3, 5}Dictionary comprehensions are excellent for building lookup tables or reversing a dictionary: {v: k for k, v in original.items()}. Set comprehensions automatically remove duplicates, which is useful for collecting unique values from a list.
Generator Expressions
Same syntax but with parentheses instead of brackets. Memory efficient for large data:
# List comprehension (creates full list in memory)
squares_list = [x**2 for x in range(1000000)]
# Generator expression (lazy evaluation)
squares_gen = (x**2 for x in range(1000000))
# Use with sum, max, min
total = sum(x**2 for x in range(1000))A generator expression produces values one at a time on demand, never building the entire list in memory. This is essential when working with large datasets or infinite sequences. When a generator expression is the sole argument to a function like sum, max, or min, you can omit the outer parentheses: sum(x**2 for x in range(1000)).
Practical Examples
These real-world patterns demonstrate what comprehensions excel at:
# Read file, strip newlines, skip blanks
lines = [line.strip() for line in open("file.txt") if line.strip()]
# Convert temperatures
celsius = [0, 10, 20, 30, 40]
fahrenheit = [(c * 9/5) + 32 for c in celsius]
# [32.0, 50.0, 68.0, 86.0, 104.0]
# Extract specific field from list of dicts
users = [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]
names = [u["name"] for u in users]
# ['Alice', 'Bob']
# Get unique characters from a string
unique = {c for c in "hello world" if c != " "}
# {'h', 'e', 'l', 'o', 'w', 'r', 'd'}The file-reading pattern is a concise way to load a configuration file into a list of non-empty lines. The temperature conversion shows a pure mapping with no filter. Extracting a field from a list of dicts is something you will do constantly when processing JSON API responses.
When NOT to Use List Comprehensions
As powerful as comprehensions are, they are not always the right tool. Readability and intent matter more than brevity.
Complex operations — use a regular loop:
# Too complex for comprehension
processed = []
for item in data:
result = expensive_function(item)
if result and validate(result):
processed.append(transform(result))
# Don't try to compress this into a comprehensionIf you need to break early, update multiple variables, or handle exceptions per element, a regular loop is clearer.
Side effects — don’t use comprehension for printing, writing files:
# Bad (creates list [None, None, None] unnecessarily)
[print(x) for x in data]
# Good
for x in data:
print(x)A comprehension that produces a list of None values solely for side effects is both wasteful and confusing to readers. Comprehensions should be about building a collection, not about executing side effects.
Readability Rule of Thumb
If you have to write a multi-line comprehension or add a comment explaining it, use a regular for-loop instead. A helpful guideline: if the comprehension does not fit on a single 79-character line, it is probably better written as a loop with an intermediate result variable.
Related: Learn Python string methods and error handling.
Nested Comprehensions
List comprehensions can nest to handle multi-dimensional data. A nested comprehension [x for row in matrix for x in row] flattens a 2D list. The order of for clauses follows the same order as nested for loops: outer loop first, inner loops after. More than two or three levels of nesting makes comprehensions hard to read — at that point, regular loops are more maintainable.
Dict and Set Comprehensions
Python extends the comprehension syntax to dictionaries and sets. Dict comprehensions {k: v for k, v in iterable} create dictionaries inline. Set comprehensions {x for x in iterable} create sets. Generator expressions (x for x in iterable) use the same syntax with parentheses, producing values lazily without storing the entire sequence in memory.
FAQ
What is the most important thing to remember? Focus on understanding the core concepts thoroughly before moving to advanced topics. Mastery comes from practice, not just reading.
How long does it take to learn this? The timeline varies by individual, but consistent daily practice of 30-60 minutes yields visible progress within weeks.
What are common mistakes beginners make? The most frequent errors include skipping fundamentals, not testing assumptions, and trying to learn too many things simultaneously.