Python List Comprehensions: From Loops to Elegant One-Liners
Learn how list comprehensions work, when they make your code clearer, and when a plain for-loop is still the better choice.
On this page
If you've read other people's Python code, you've almost certainly seen something like this:
squares = [n * n for n in range(10)]That's a list comprehension: a compact way to build a new list from an existing iterable. Once it clicks, you'll use it constantly. In this guide we'll build up from the plain for loop you already know, cover filtering and transformation, look at dictionary and set comprehensions, and finish with the cases where you shouldn't use one.
Start with the loop you already know#
Here's the classic way to build a list of squares:
squares = []
for n in range(10):
squares.append(n * n)
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]Three lines, three jobs: create an empty list, loop, append. A comprehension expresses the same idea in one line:
squares = [n * n for n in range(10)]Read it left to right as "give me n * n for each n in range(10)." The expression you want to collect comes first, followed by the loop that produces the values.
The anatomy of a comprehension#
Every list comprehension follows this shape:
[expression for item in iterable if condition]| Part | Meaning | Required? |
|---|---|---|
expression |
What to put in the new list | Yes |
for item in iterable |
Where the values come from | Yes |
if condition |
Keep only items that pass the test | No |
Filtering with if#
Add a condition at the end to keep only some items. Here we keep the even numbers:
evens = [n for n in range(20) if n % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]You can transform and filter at the same time. This collects the lengths of words longer than three characters:
words = ["git", "python", "sql", "linux", "css", "docker"]
long_word_lengths = [len(w) for w in words if len(w) > 3]
# [6, 5, 6]Choosing a value with if ... else#
What if you want to keep every item but change some of them? Put an if ... else before the for. This is Python's conditional expression, not a filter:
scores = [92, 47, 78, 30, 85]
results = ["pass" if s >= 50 else "fail" for s in scores]
# ['pass', 'fail', 'pass', 'fail', 'pass']Remember the position rule
An if after the for filters items out. An if ... else before the for chooses what each item becomes. The first can shrink your list; the second never does.
Real-world examples#
Comprehensions shine when you're cleaning or reshaping data.
Normalising user input:
raw_emails = [" Ada@Example.com", "grace@example.com ", "", "LINUS@example.com"]
emails = [e.strip().lower() for e in raw_emails if e.strip()]
# ['ada@example.com', 'grace@example.com', 'linus@example.com']Extracting a field from a list of dictionaries:
users = [
{"name": "Ada", "active": True},
{"name": "Grace", "active": False},
{"name": "Linus", "active": True},
]
active_names = [u["name"] for u in users if u["active"]]
# ['Ada', 'Linus']Flattening a nested list. The loops appear in the same order as they would in nested for statements:
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [value for row in matrix for value in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]Dictionary and set comprehensions#
The same syntax works with curly braces. Use key: value to build a dictionary:
words = ["git", "python", "sql"]
lengths = {w: len(w) for w in words}
# {'git': 3, 'python': 6, 'sql': 3}Drop the colon and you get a set, which removes duplicates automatically:
unique_lengths = {len(w) for w in words}
# {3, 6}Generator expressions: comprehensions without the list#
Swap the square brackets for parentheses and you get a generator expression. It produces values one at a time instead of building the whole list in memory, which is perfect when you only need to consume the values once:
total = sum(n * n for n in range(1_000_000))When a generator expression is the only argument to a function, you can drop the extra parentheses, as above. Functions like sum(), any(), all(), max() and min() pair beautifully with them:
has_admin = any(u.get("role") == "admin" for u in users)When not to use a comprehension#
Comprehensions exist to make code more readable. If one stops being readable, use a loop. Watch for these warning signs:
- It doesn't fit comfortably on one or two lines. Nested comprehensions with several conditions are hard to scan.
- You need side effects. Don't write
[print(x) for x in items]. It builds a throwaway list ofNonevalues. Use aforloop. - You need
try/exceptor several statements per item. A comprehension holds a single expression; a loop or a helper function is clearer.
# Hard to read: avoid
result = [x * y for x in range(10) if x % 2 for y in range(x) if y > 2 and x + y < 12]
# Clearer: give the logic a name
def odd_pair_products(limit):
for x in range(1, limit, 2):
for y in range(3, x):
if x + y < 12:
yield x * y
result = list(odd_pair_products(10))Recap#
[expr for x in items]transforms every item.[expr for x in items if cond]filters as well.[a if cond else b for x in items]chooses a value for each item.- Use
{k: v ...}for dicts,{x ...}for sets and(x ...)for lazy generators. - If it's getting clever, a loop is usually kinder to the next reader, who might be you.
Practice: open a script you wrote recently and look for an "empty list, loop, append" pattern. Rewrite it as a comprehension, then decide honestly which version reads better.