List, Dict, and Set Comprehensions
Comprehensions let you build collections from existing data in a single, readable line. They replace verbose for loops with expressions that are both cleaner and faster. Once you see how they work, you’ll reach for them constantly.
This tutorial covers list comprehensions first since they’re the most common, then shows how the same pattern applies to dictionaries and sets.
List Comprehensions
A list comprehension creates a new list by transforming or filtering an existing iterable. The basic syntax has three parts: the expression, the iteration, and an optional condition.
# Traditional for loop
squares = []
for x in range(10):
squares.append(x ** 2)
# List comprehension — same result, fewer lines
squares = [x ** 2 for x in range(10)]
print(squares)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Read it left to right: “x squared, for each x in range(10).” That reading direction matches how Python evaluates the comprehension. The expression on the left is applied to every element produced by the iteration on the right. This left-to-right flow is one reason comprehensions feel more natural than the equivalent for-loop once you get comfortable with the syntax.
Filtering with Conditions
An if clause at the end of a comprehension is a filter, keeping only the elements that satisfy the condition. The filtering happens during iteration rather than afterward, which means the comprehension never builds intermediate lists that get thrown away.
# Only even numbers
evens = [x for x in range(20) if x % 2 == 0]
print(evens)
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# Words with more than 4 characters
words = ["cat", "elephant", "dog", "hippopotamus"]
long_words = [w for w in words if len(w) > 4]
print(long_words)
# ['elephant', 'hippopotamus']
Transforming Data
The expression on the left side of a comprehension can be more than just a variable name. You can call methods on each element, apply a function to transform the data, or construct entirely new objects. This flexibility means a single comprehension can replace several lines of loop code while remaining readable, provided the expression stays concise enough to grasp in one glance.
# Convert strings to uppercase
names = ["alice", "bob", "charlie"]
upper_names = [name.upper() for name in names]
print(upper_names)
# ['ALICE', 'BOB', 'CHARLIE']
# Parse numbers from strings
strings = ["10", "20", "abc", "30"]
numbers = [int(s) for s in strings if s.isdigit()]
print(numbers)
# [10, 20]
Nested Loops
Comprehensions support nested iteration by writing one loop after another, with the outer loop appearing first. This is the same order you would use in nested for-loops, and it can flatten a nested structure or generate a Cartesian product of two collections in a single expression.
# Flatten a matrix into a single list
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [num for row in matrix for num in row]
print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Generate all combinations
suits = ["hearts", "diamonds", "clubs", "spades"]
ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
cards = [f"{rank} of {suit}" for suit in suits for rank in ranks]
print(len(cards)) # 52
This flattens the nested structure by pulling from the outer loop first, then the inner loop. The result is a single flat list with all combinations. Notice that the comprehension above generates every possible card in a standard 52-card deck. That task would look considerably more verbose with traditional nested for-loops and an explicit append call.
Dictionary Comprehensions
Dictionary comprehensions follow the same pattern as list comprehensions but produce key-value pairs instead of single values. The syntax requires both a key expression and a value expression separated by a colon, mirroring the structure of a dictionary literal. This makes them feel familiar even on first encounter.
# Create a dictionary mapping numbers to their squares
squares_dict = {x: x ** 2 for x in range(5)}
print(squares_dict)
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
Filtering and Transforming
You can filter using an if clause and transform both keys and values:
# Invert a dictionary
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
print(inverted)
# {1: 'a', 2: 'b', 3: 'c'}
# Filter by value
prices = {"apple": 0.50, "banana": 0.30, "cherry": 2.00, "date": 1.50}
affordable = {fruit: price for fruit, price in prices.items() if price < 1.00}
print(affordable)
# {'apple': 0.5, 'banana': 0.3}
# Transform keys and values together
words = ["cat", "dog", "elephant"]
word_lengths = {word: len(word) for word in words}
print(word_lengths)
# {'cat': 3, 'dog': 3, 'elephant': 8}
Practical Example: Counting
Dictionary comprehensions really earn their keep when you need to count or group items from a collection. Instead of initializing an empty dictionary and writing a for-loop with conditional updates, you can express the entire counting operation in a single declarative statement. The comprehension reads like a specification of what you want rather than a recipe for how to build it step by step.
# Count character frequencies in a string
text = "hello world"
char_counts = {char: text.count(char) for char in set(text)}
print(char_counts)
# {'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}
Set Comprehensions
Set comprehensions use curly braces instead of square brackets, and the semantics follow naturally: instead of building an ordered list with possible duplicates, you build an unordered collection that enforces uniqueness. Everything else about the syntax, including the expression, the iteration, and the optional filter, works exactly the same as it does for list comprehensions.
# Create a set of unique word lengths
words = ["cat", "dog", "elephant", "mouse", "bear"]
lengths = {len(word) for word in words}
print(lengths)
# {3, 5, 8, 4}
Notice that the words “cat” and “dog” both have a length of three, but the set only stores that value once. Set comprehensions are especially useful when you want to eliminate duplicates while transforming data, which would otherwise require an extra call to set() after building a list.
Filtering with Set Comprehensions
You can apply the same if-clause filtering to set comprehensions that you use with lists and dictionaries. The filter eliminates elements before they are added to the set, so the result is both filtered and deduplicated in a single pass:
# Extract unique first letters
names = ["Alice", "Bob", "Anna", "Ben", "Andrew"]
first_letters = {name[0] for name in names}
print(first_letters)
# {'A', 'B'}
# Get numbers divisible by 3 from a list with duplicates
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9]
divisible_by_3 = {n for n in numbers if n % 3 == 0}
print(divisible_by_3)
# {3, 6, 9}
When to Use Comprehensions
Comprehensions are powerful but they are not always the right choice. A comprehension works best when the transformation logic is simple enough to read in a single glance, using one or two short expressions. When the logic gets complex, or when the body would need multiple statements or exception handling, a traditional for-loop is almost always clearer.
Use comprehensions when:
- Transforming or filtering a collection in a straightforward way
- You want better performance than a for loop with
.append() - The result makes the code clearer than the equivalent loop
Use a regular for loop when:
- The body has multiple statements
- You need to handle exceptions during iteration
- The logic is too complex to read as a single expression
# This is fine as a comprehension
squares = [x ** 2 for x in range(10)]
# This should be a for loop — it handles errors
results = []
for item in items:
try:
results.append(process(item))
except ValueError:
results.append(None)
Generator Expressions
Sometimes you do not need all the values at once. A generator expression uses the same syntax as a comprehension but with parentheses instead of brackets or braces. Instead of building the entire collection in memory immediately, a generator produces each value on demand as you iterate over it. This lazy evaluation can save significant memory when the source data is large or the result is only consumed once.
# Generator expression — computes on demand
squares_gen = (x ** 2 for x in range(10))
# List comprehension — computes everything immediately
squares_list = [x ** 2 for x in range(10)]
print(next(squares_gen)) # 0 — only this value is computed
print(list(squares_gen)) # [1, 4, 9, 16, 25, 36, 49, 64, 81]
Generator expressions save memory when working with large datasets. Use them when you only need to iterate once.
Next Steps
Now you know how to build lists, dictionaries, and sets using comprehensions. These skills apply throughout Python codebases.
Continue with the next tutorial in the series to learn about *args and **kwargs, which let you build functions that accept any number of arguments.