Python List Comprehensions, Step by Step
Learn Python list comprehensions step by step: turn any loop into one clean line, filter with if, and know exactly when a plain loop is the better choice.
List comprehensions are the first piece of Python that makes beginners feel like they're reading a foreign dialect. You're comfortable with loops, then you meet a line like [n ** 2 for n in numbers if n % 2 == 0] and your eyes bounce off it.
Here's the secret: a comprehension is not new logic. It's a loop you already know how to write, folded into one line by a fixed, mechanical rule. Once you learn the rule, you can convert any simple loop into a comprehension and, just as importantly, read anyone else's.
The transformation, shown mechanically
Start with a loop you'd write on day one: build a list of squares.
squares = []
for number in range(1, 6):
squares.append(number ** 2)
print(squares) # [1, 4, 9, 16, 25]
Three parts are doing the work here:
- An empty list:
squares = [] - A loop:
for number in range(1, 6) - An append with an expression:
squares.append(number ** 2)
The comprehension takes those exact parts and rearranges them. The expression from inside append() moves to the front, the for line follows it, and the brackets replace both the empty list and the append call:
squares = [number ** 2 for number in range(1, 6)]
print(squares) # [1, 4, 9, 16, 25]
Same variable name, same range, same expression, same result. Nothing was invented; the pieces just moved. Every list comprehension you will ever write is this same rearrangement:
result = [expression for item in iterable]
When you're unsure how to write one, write the loop first, then fold it. After a few weeks the folding happens in your head.
How to read one: start in the middle
The reading order is the part nobody tells beginners. You don't read a comprehension left to right like English. You read it middle first:
- Find the
forclause:for number in range(1, 6). This tells you what's being looped over. - Then read the expression at the front:
number ** 2. This tells you what each item becomes. - Finally, check the end for an
if. That tells you which items are kept.
So [name.upper() for name in guests] reads as: "go through guests, and for each name, produce name.upper()." Once the middle-first habit clicks, dense comprehensions in other people's code stop being intimidating.
Filtering with if
To keep only some items, a plain loop uses an if before the append:
evens = []
for n in range(10):
if n % 2 == 0:
evens.append(n)
In a comprehension, that condition goes at the end:
evens = [n for n in range(10) if n % 2 == 0]
print(evens) # [0, 2, 4, 6, 8]
The if acts as a gate: items that fail the test are skipped entirely, and nothing is added for them. This is the most common comprehension shape in real code, used constantly for cleaning data:
raw = [" alice ", "", "bob", " ", "carol"]
names = [entry.strip() for entry in raw if entry.strip()]
print(names) # ['alice', 'bob', 'carol']
if/else is a different thing, in a different place
Here's a rule that trips up almost everyone once: a filtering if goes at the end, but an if/else that chooses between two values goes at the front, inside the expression:
numbers = [1, 2, 3, 4, 5]
# Filter: keep only some items (if at the end)
evens = [n for n in numbers if n % 2 == 0]
# [2, 4]
# Transform: keep every item, choose its value (if/else at the front)
labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
# ['odd', 'even', 'odd', 'even', 'odd']
They aren't interchangeable. The end if decides whether an item appears; the front if/else decides what it becomes, and every item appears. Writing [n for n in numbers if n % 2 == 0 else 0] is a syntax error, and now you know why: a filter has no else.
You can combine both when you genuinely need to filter and transform:
scores = [45, 88, 92, 61, 79]
curved = [score + 5 if score < 90 else score for score in scores if score >= 60]
# [93, 92, 66, 84]
Notice that this line is already getting hard to read. Hold that thought for the readability section.
Nested comprehensions, and when to say no
A comprehension can contain more than one for. The classic use is flattening a list of lists:
matrix = [[1, 2, 3], [4, 5, 6]]
flat = [cell for row in matrix for cell in row]
print(flat) # [1, 2, 3, 4, 5, 6]
The for clauses run left to right in the same order as nested loops: for row in matrix is the outer loop, for cell in row is the inner one. If you'd write the loops in that order, you write the clauses in that order.
flat = []
for row in matrix: # first for clause
for cell in row: # second for clause
flat.append(cell)
Now the honest advice: two for clauses is the ceiling, and even that deserves a second look. A comprehension with three loops, or nested brackets inside brackets, is a puzzle, not a convenience. Future readers, including future you, will silently curse it. When logic needs nesting plus conditions plus transformation, a plain loop with good variable names is the professional choice, not the beginner one.
Dict and set comprehensions
The same folding rule builds dictionaries and sets. Swap the square brackets for curly braces.
A dict comprehension uses a key: value expression:
words = ["apple", "banana", "cherry"]
lengths = {word: len(word) for word in words}
print(lengths) # {'apple': 5, 'banana': 6, 'cherry': 6}
A set comprehension looks like a list comprehension in curly braces, and it automatically drops duplicates:
names = ["Ada", "Alan", "Grace", "Guido"]
initials = {name[0] for name in names}
print(initials) # {'A', 'G'}
Both support the same if filtering and read the same middle-first way. If you can read a list comprehension, you already read these.
Generator expressions, briefly
Use parentheses instead of brackets and you get a generator expression: it produces items one at a time instead of building the whole list in memory.
total = sum(n ** 2 for n in range(1_000_000))
This never creates a million-element list; each square is produced, added to the running total, and discarded. When you're feeding results straight into sum(), max(), min(), or any(), the generator version is the better default for large inputs. That's the whole introduction you need for now; just recognize the parentheses when you see them.
Readability guidelines that actually hold up
Comprehensions exist to make code clearer, and they stop doing that fast. Rules of thumb that working Python programmers mostly agree on:
- One line, roughly 79 characters. If it wraps, it probably wants to be a loop.
- One
for, oneifis the sweet spot. Twoforclauses only for simple flattening. - No side effects. A comprehension builds a value. If you're calling
print()or appending to some other list inside one, use a loop. - Name the variable honestly.
[u.email for u in users]reads fine;[x.email for x in y]doesn't. - When in doubt, write the loop. Nobody was ever confused by a clear for loop. Plenty of people have been confused by a clever comprehension.
The goal is never to prove you can compress logic. It's to make the reader's job easier, and sometimes the loop does that better.
Common beginner errors
Using a comprehension for its side effects. [print(n) for n in numbers] works but builds a useless list of None values. If you're not keeping the result, you want a loop.
Putting the filter if in front. [if n > 0 n for n in nums] is a syntax error. Filters go at the end; only if/else value choices go at the front.
Reusing the loop variable name. data = [data for data in data] style collisions cause genuinely confusing bugs. Keep the loop variable distinct from the list it walks.
Expecting break to work. There's no way to stop a comprehension early. If you need to stop at the first match, use a loop or next() with a generator expression.
Modifying the list you're iterating. Comprehensions build a new list; that's their model. Trying to remove items from the original inside one means you wanted a different tool.
Wrong order in nested comprehensions. [cell for cell in row for row in matrix] fails with a NameError because row isn't defined yet. The for clauses must appear in outer-to-inner order, exactly like the loops they replace.
Frequently asked questions
Are list comprehensions faster than for loops?
Usually somewhat, yes: the looping happens in optimized C rather than through repeated append() calls in Python bytecode. But the difference is rarely why you'd choose one. Choose the comprehension because it's clearer for simple transforms, not to chase micro-speed.
When should I use a plain loop instead?
Whenever the logic has multiple steps, side effects, error handling, or needs to stop early, and whenever the comprehension would be hard to read at a glance. A comprehension answers one question: "what does each item become, and which items are kept?" If your logic doesn't fit that sentence, use a loop.
Is there a tuple comprehension?
No. Parentheses give you a generator expression, not a tuple. If you need a tuple, wrap it explicitly: tuple(n ** 2 for n in range(5)).
Can a comprehension replace every loop?
No, and it shouldn't. Comprehensions replace one specific pattern: build a new collection from an existing iterable. Loops that update counters, coordinate multiple collections, or perform actions rather than build values should stay loops.
Practice the fold
You now have the whole mental model: write the loop, fold it with the fixed rule, read it middle-first, and downgrade to a loop the moment clarity suffers. The skill sticks when you convert real loops in your own code, so try folding two or three today.
If you want guided practice with instant feedback, comprehensions are covered hands-on in our free Python Essentials course, and the Python Intermediate track builds on them with generators, iterators, and the patterns working Python code is made of.
Kodion Team
Kodion Editorial
Written by the team that builds Kodion's courses: working engineers who test every example in the interactive editor before it ships. How we create and review content
Continue Learning
📚 Related Articles
Python Basics: Variables, Loops, and Functions
Master the fundamentals of Python programming with practical examples. Learn variables, data types, loops, and functions to build a strong foundation.
Build Your First REST API: A Beginner's Roadmap
Design and build a working REST API in Python with FastAPI: endpoints, Pydantic models, status codes, and curl tests, explained step by step.