How do list, dict, and set comprehensions work in Python, and when should you avoid them?
Comprehensions are syntactic sugar for building a new collection by iterating over an iterable and optionally filtering elements. They are faster than equivalent for-loops because the iteration runs at the C level inside the interpreter. Avoid them when the expression is too complex to read at a glance — a plain loop with descriptive variable names is preferable.
How to think about it
“It’s a shorter for loop” is the answer that earns a polite nod and a harder follow-up. A strong answer covers all three forms — list, dict, and set — explains why they run faster than a hand-written loop, knows the lazy generator-expression cousin, and, just as importantly, knows when a plain loop is the better call.
The shape is always the same: [expression for item in iterable if condition]. Only the brackets change to pick the container:
# List — square brackets
squares = [x**2 for x in range(10) if x % 2 == 0]
# [0, 4, 16, 36, 64]
# Dict — braces with a colon
word_lengths = {word: len(word) for word in ["apple", "fig", "banana"]}
# {'apple': 5, 'fig': 3, 'banana': 6}
# Set — braces, no colon (deduplicates for free)
domains = {email.split("@")[1] for email in ["a@x.com", "b@x.com", "c@y.com"]}
# {'x.com', 'y.com'}
# Generator — parentheses — lazy, O(1) memory
total = sum(x**2 for x in range(1_000_000))
Why they beat a plain loop
A list comprehension compiles to a dedicated LIST_APPEND bytecode that runs in C. A hand-written loop calling result.append(x) has to look up the append attribute on the list every single iteration; the comprehension skips that lookup entirely, which is where the typical 20–50% speedup comes from.
A worked example
# List comprehension with a filter
squares_of_evens = [x**2 for x in range(1, 11) if x % 2 == 0]
print("Squares of evens:", squares_of_evens)
# Dict comprehension — invert a mapping
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
print("Inverted dict:", inverted)
# Set comprehension — unique first letters (sets dedupe automatically)
words = ["apple", "avocado", "banana", "blueberry", "cherry"]
first_letters = {w[0] for w in words}
print("First letters (set):", sorted(first_letters))
# Nested comprehension — flatten a 2-D list (loops read left to right)
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [val for row in matrix for val in row]
print("Flattened matrix:", flat)
# Dict comprehension — a word-frequency count
sentence = "the cat sat on the mat"
freq = {word: sentence.split().count(word) for word in sorted(set(sentence.split()))}
print("Word frequencies:", freq)
Squares of evens: [4, 16, 36, 64, 100]
Inverted dict: {1: 'a', 2: 'b', 3: 'c'}
First letters (set): ['a', 'b', 'c']
Flattened matrix: [1, 2, 3, 4, 5, 6, 7, 8, 9]
Word frequencies: {'cat': 1, 'mat': 1, 'on': 1, 'sat': 1, 'the': 2}
The nested one is worth a second look. [val for row in matrix for val in row] reads in the same order as the loop it replaces — outer loop first, inner loop second:
for row in matrix:
for val in row:
flat.append(val)
When a plain loop wins
The moment a comprehension needs more than a glance to parse, reach for the loop — readability beats cleverness:
# Too dense to debug or extend
result = [transform(x) for x in data if predicate(x) if secondary(x)]
# Clearer, and far easier to step through
result = []
for x in data:
if predicate(x) and secondary(x):
result.append(transform(x))