Python List Comprehensions: From Loops to One-Liners
๐ Free: AI Publishing Checklist โ 7 steps in Python ยท Full pipeline: germy5.gumroad.com/l/xhxkzz (pay what you want, min $9.99)
You've written this a hundred times:
squares = []
for x in range(10):
squares.append(x ** 2)
There's nothing wrong with it โ but Python gives you a shorter, faster, and more readable way to express the same idea. By the end of this article you'll be writing that as:
squares = [x ** 2 for x in range(10)]
Let's build up from zero.
The Basic Syntax
The structure of a list comprehension is:
[expression for item in iterable]
Read it left to right: "give me expression, for each item in iterable."
Before:
names = ["alice", "bob", "carol"]
upper = []
for name in names:
upper.append(name.upper())
# ['ALICE', 'BOB', 'CAROL']
After:
upper = [name.upper() for name in names]
# ['ALICE', 'BOB', 'CAROL']
Same result. One line. No .append() boilerplate.
Another before/after โ doubling numbers:
# Before
doubled = []
for n in [1, 2, 3, 4]:
doubled.append(n * 2)
# After
doubled = [n * 2 for n in [1, 2, 3, 4]]
# [2, 4, 6, 8]
Filtering with if
Add a condition at the end to keep only the items you want:
[expression for item in iterable if condition]
Before:
nums = [-3, -1, 0, 2, 5, 8]
positives = []
for x in nums:
if x > 0:
positives.append(x)
After:
positives = [x for x in nums if x > 0]
# [2, 5, 8]
Filtering strings by length:
words = ["hi", "hello", "hey", "howdy", "yo"]
# Before
long_words = []
for w in words:
if len(w) > 3:
long_words.append(w)
# After
long_words = [w for w in words if len(w) > 3]
# ['hello', 'howdy']
Ternary (Conditional) Expression Inside
Sometimes you want to transform every item but differently based on a condition. Use the ternary form value_if_true if condition else value_if_false inside the expression slot:
[x if condition else y for x in iterable]
Before:
nums = [-3, 1, -2, 4, 0]
clipped = []
for x in nums:
if x > 0:
clipped.append(x)
else:
clipped.append(0)
After:
clipped = [x if x > 0 else 0 for x in nums]
# [0, 1, 0, 4, 0]
Notice the difference: if at the end filters items out; if/else inside the expression keeps all items but transforms them.
Nested Loops
You can nest loops by chaining for clauses:
[f(x, y) for x in a for y in b]
Before:
pairs = []
for x in [1, 2]:
for y in ["a", "b"]:
pairs.append((x, y))
After:
pairs = [(x, y) for x in [1, 2] for y in ["a", "b"]]
# [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
Keep nesting to one level in comprehensions. Two or more levels of nesting is the point where you should switch back to regular loops (more on that below).
Dict Comprehensions
The same idea works for dictionaries:
{key_expression: value_expression for item in iterable}
Before:
names = ["alice", "bob", "carol"]
name_lengths = {}
for name in names:
name_lengths[name] = len(name)
After:
name_lengths = {name: len(name) for name in names}
# {'alice': 5, 'bob': 3, 'carol': 5}
Dict comprehensions are especially useful for inverting a mapping:
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}
Set Comprehensions
Use curly braces without a colon to get a set (unique values, unordered):
{expression for item in iterable}
words = ["apple", "banana", "apple", "cherry", "banana"]
# Deduplicate and uppercase in one step
unique_upper = {w.upper() for w in words}
# {'APPLE', 'BANANA', 'CHERRY'}
Generator Expressions
Swap the square brackets for parentheses and you get a generator โ lazy evaluation, no list built in memory:
(expression for item in iterable)
# List comprehension โ builds entire list immediately
squares_list = [x ** 2 for x in range(1_000_000)]
# Generator expression โ computes each value on demand
squares_gen = (x ** 2 for x in range(1_000_000))
When to prefer a generator:
- You only need to iterate once (e.g., pass directly into
sum(),max(), or a loop) - The data set is large and you don't need all values at once
# No need to build a full list just to sum it
total = sum(x ** 2 for x in range(1_000_000))
Pass a generator directly to any function that accepts an iterable and you avoid storing the whole thing in memory.
Quick Reference Table
| Type | Syntax | Returns |
|---|---|---|
| List | [expr for x in it] |
list โ eager, ordered, allows duplicates |
| Dict | {k: v for x in it} |
dict โ eager, key-value pairs |
| Set | {expr for x in it} |
set โ eager, unique values, unordered |
| Generator | (expr for x in it) |
generator โ lazy, single-pass |
Real Pipeline Examples
These patterns come directly from the kind of automation pipelines Python developers use every day.
Filtering tasks by status (like a to-do pipeline):
tasks = [
{"id": 1, "title": "Draft chapter 1", "done": True},
{"id": 2, "title": "Add examples", "done": False},
{"id": 3, "title": "Proofread", "done": False},
]
pending = [t["title"] for t in tasks if not t["done"]]
# ['Add examples', 'Proofread']
Extracting fields from an API response:
api_response = [
{"id": "ch01", "title": "Introduction", "words": 800},
{"id": "ch02", "title": "Setup", "words": 1200},
{"id": "ch03", "title": "Core Concepts","words": 2100},
]
chapter_ids = [chapter["id"] for chapter in api_response]
# ['ch01', 'ch02', 'ch03']
long_chapters = [c["title"] for c in api_response if c["words"] > 1000]
# ['Setup', 'Core Concepts']
Building a lookup dict from a list:
users = [
{"username": "alice", "role": "admin"},
{"username": "bob", "role": "editor"},
{"username": "carol", "role": "viewer"},
]
role_lookup = {u["username"]: u["role"] for u in users}
# {'alice': 'admin', 'bob': 'editor', 'carol': 'viewer'}
print(role_lookup["bob"]) # 'editor'
When NOT to Use Comprehensions
Comprehensions are great until they aren't. Here are the warning signs:
Two or more filter conditions โ use a regular loop:
# Hard to read at a glance
result = [x for x in data if x > 0 if x % 2 == 0 if x < 100]
# Much clearer
result = []
for x in data:
if x > 0 and x % 2 == 0 and x < 100:
result.append(x)
Nesting beyond one level โ use a regular loop:
# Painful to read
flat = [cell for row in matrix for col in row for cell in col]
# Easier to follow
flat = []
for row in matrix:
for col in row:
for cell in col:
flat.append(cell)
Side effects โ use a regular loop:
# Never do this โ comprehensions are for building values, not side effects
[print(x) for x in items] # bad practice
# Do this instead
for x in items:
print(x)
The rule of thumb: if the comprehension doesn't fit on one line without squinting, convert it to a loop.
Performance Note
List comprehensions are measurably faster than equivalent for loops with .append() because they're optimized at the bytecode level โ Python doesn't have to look up the .append method on every iteration. For most day-to-day code the difference is small, but at scale (tens of thousands of items) comprehensions consistently win. Generator expressions win on memory when you don't need the full list.
Closing CTA
The pipeline uses list comprehensions to filter tasks, extract chapter IDs, and build tag lookups in a single expression: germy5.gumroad.com/l/xhxkzz โ pay what you want, min $9.99.
Top comments (0)