A list comprehension is Python’s concise syntax for creating a new list by applying an expression to every item in an iterable, with an optional filter condition. Instead of writing a for loop that appends to an empty list, a comprehension does it in a single, readable line. They are one of the most distinctly Pythonic constructs — you will see them everywhere in Python code, including FastAPI response transformations, SQLAlchemy result processing, and data pipeline logic. The key is knowing when a comprehension improves readability and when a regular loop is clearer — comprehensions should be simple enough to read in a single glance.
List Comprehension Syntax
# Syntax: [expression for item in iterable if condition]
# The 'if condition' part is optional
# ── Basic: transform every item ────────────────────────────────────────────────
numbers = [1, 2, 3, 4, 5]
# Traditional for loop
squares = []
for n in numbers:
squares.append(n ** 2)
# squares = [1, 4, 9, 16, 25]
# List comprehension — one line
squares = [n ** 2 for n in numbers]
# [1, 4, 9, 16, 25]
# ── With filter: only include some items ───────────────────────────────────────
# Traditional
evens = []
for n in numbers:
if n % 2 == 0:
evens.append(n)
# Comprehension
evens = [n for n in numbers if n % 2 == 0]
# [2, 4]
# ── Transform AND filter ───────────────────────────────────────────────────────
even_squares = [n ** 2 for n in numbers if n % 2 == 0]
# [4, 16]
n, item, post) is local to the comprehension in Python 3 — it does not leak into the surrounding scope, unlike Python 2 where loop variables from comprehensions would overwrite outer variables of the same name.Practical FastAPI Examples
# ── Extracting fields from database results ────────────────────────────────────
db_posts = [
{"id": 1, "title": "First", "body": "...", "author_id": 1, "published": True},
{"id": 2, "title": "Second", "body": "...", "author_id": 2, "published": False},
{"id": 3, "title": "Third", "body": "...", "author_id": 1, "published": True},
]
# Get titles of published posts
published_titles = [p["title"] for p in db_posts if p["published"]]
# ["First", "Third"]
# Build a list of IDs
post_ids = [p["id"] for p in db_posts]
# [1, 2, 3]
# ── Transforming response data ─────────────────────────────────────────────────
# Add a "slug" field based on the title
def to_slug(title: str) -> str:
return title.lower().replace(" ", "-")
posts_with_slugs = [
{**p, "slug": to_slug(p["title"])} # unpack dict and add slug field
for p in db_posts
]
# [{"id":1, "title":"First", ..., "slug":"first"}, ...]
# ── String processing ──────────────────────────────────────────────────────────
tags_input = " python , fastapi , postgresql "
tags = [tag.strip() for tag in tags_input.split(",")]
# ["python", "fastapi", "postgresql"]
# Normalise tags: lowercase, strip, remove empty
raw_tags = ["Python", " FastAPI ", "", "PostgreSQL", "python"]
clean_tags = list({tag.strip().lower() for tag in raw_tags if tag.strip()})
# ["python", "fastapi", "postgresql"] (unique, sorted)
Comprehension with Conditionals — if/else Inside
# The filter 'if' goes at the END of the comprehension
# An if/else INSIDE the expression goes BEFORE the for
numbers = [1, 2, 3, 4, 5]
# Filter (if at end — keeps only some items)
odds = [n for n in numbers if n % 2 != 0]
# [1, 3, 5]
# Transform with ternary (if/else before for — transforms ALL items)
labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
# ["odd", "even", "odd", "even", "odd"]
# Combine: transform then filter
result = [n * 10 if n > 2 else n for n in numbers if n != 4]
# n=1: 1 (not >2), n=2: 2 (not >2), n=3: 30 (>2), n=5: 50 (>2)
# [1, 2, 30, 50] (4 was filtered out)
Nested List Comprehensions
# Nested comprehension — for 2D structures
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# Flatten a 2D list to 1D
flat = [num for row in matrix for num in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Read: "for each row in matrix, for each num in row, take num"
# Transpose a matrix
transposed = [[row[i] for row in matrix] for i in range(3)]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# Simple nested — all combinations
colours = ["red", "blue"]
sizes = ["S", "M", "L"]
combos = [f"{c}-{s}" for c in colours for s in sizes]
# ["red-S", "red-M", "red-L", "blue-S", "blue-M", "blue-L"]
Common Mistakes
Mistake 1 — Confusing filter position with ternary position
❌ Wrong — putting ternary after the for (syntax error) or filter before for:
[n for n in numbers if n % 2 == 0 else n * 10] # SyntaxError
✅ Correct — ternary goes BEFORE the for, filter goes AFTER:
[n if n % 2 == 0 else n * 10 for n in numbers] # transform all ✓
[n for n in numbers if n % 2 == 0] # filter only ✓
Mistake 2 — Over-complex comprehensions
❌ Wrong — unreadable one-liner:
result = [f(x) for x in data if g(x) and h(x) for y in x.items() if y[0] != "skip"]
✅ Correct — use a regular for loop when logic is complex:
result = []
for x in data:
if g(x) and h(x):
for key, val in x.items():
if key != "skip":
result.append(f(x))
# Clear intent, easy to debug ✓
Mistake 3 — Using a comprehension for side effects
❌ Wrong — comprehension just to call a function (wastes memory building a list):
[print(item) for item in items] # builds a [None, None, ...] list for nothing
✅ Correct — use a for loop for side effects:
for item in items:
print(item) # ✓ no wasted list creation
Quick Reference
| Pattern | Code |
|---|---|
| Transform all items | [expr for x in iterable] |
| Filter items | [x for x in iterable if condition] |
| Transform + filter | [expr for x in iterable if condition] |
| Ternary in expression | [a if cond else b for x in iterable] |
| Flatten 2D list | [item for row in matrix for item in row] |
| From string list | [s.strip().lower() for s in strings] |