Toolmingo
Guides8 min read

Python List Comprehensions: Complete Guide with Examples

Master Python list comprehensions — syntax, conditionals, nested comprehensions, dict and set comprehensions, when to use them vs loops, and common pitfalls.

List comprehensions are Python's most elegant feature for building lists from iterables. They replace five lines of a for loop with one expressive line — and run faster too.


Quick Reference

Pattern Syntax
Basic [expr for item in iterable]
With filter [expr for item in iterable if condition]
if/else inline [a if condition else b for item in iterable]
Nested loops [expr for x in xs for y in ys]
Dict comprehension {k: v for k, v in pairs}
Set comprehension {expr for item in iterable}
Generator expression (expr for item in iterable)

Basic Syntax

The form is: [expression for variable in iterable].

# Loop version
squares = []
for n in range(1, 6):
    squares.append(n ** 2)
# [1, 4, 9, 16, 25]

# Comprehension version
squares = [n ** 2 for n in range(1, 6)]
# [1, 4, 9, 16, 25]

Read it left-to-right: "give me n**2 for each n in range(1, 6)".


Filtering with if

Add an if clause at the end to keep only items that match a condition.

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Keep only even numbers
evens = [n for n in numbers if n % 2 == 0]
# [2, 4, 6, 8, 10]

# Keep words longer than 3 characters
words = ["hi", "hello", "hey", "world", "no"]
long_words = [w for w in words if len(w) > 3]
# ['hello', 'world']

# Filter and transform at once
even_squares = [n ** 2 for n in numbers if n % 2 == 0]
# [4, 16, 36, 64, 100]

Inline if/else (Ternary)

When you want to transform — not filter — use if/else inside the expression (before for):

numbers = [1, 2, 3, 4, 5]

# Label each number
labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
# ['odd', 'even', 'odd', 'even', 'odd']

# Clamp values to a maximum
data = [10, 200, 30, 500, 50]
clamped = [x if x <= 100 else 100 for x in data]
# [10, 100, 30, 100, 50]

Position matters:

  • [expr_if_true if cond else expr_if_false for item in iterable] — transform every item
  • [expr for item in iterable if cond] — skip items that fail the condition

Calling Functions

The expression can be any callable:

names = ["  alice ", "BOB", "  Charlie  "]

# Strip whitespace and lowercase
cleaned = [name.strip().lower() for name in names]
# ['alice', 'bob', 'charlie']

# Apply a function
import math
roots = [math.sqrt(n) for n in [4, 9, 16, 25]]
# [2.0, 3.0, 4.0, 5.0]

# Call a custom function
def score_to_grade(score):
    if score >= 90: return "A"
    if score >= 80: return "B"
    if score >= 70: return "C"
    return "F"

scores = [95, 82, 67, 74]
grades = [score_to_grade(s) for s in scores]
# ['A', 'B', 'F', 'C']

Nested Loops

Two for clauses flatten nested structures:

# Cartesian product
pairs = [(x, y) for x in [1, 2, 3] for y in ["a", "b"]]
# [(1,'a'),(1,'b'),(2,'a'),(2,'b'),(3,'a'),(3,'b')]

# Flatten a 2D list
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [n for row in matrix for n in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

# With a filter on the inner loop
filtered = [n for row in matrix for n in row if n % 2 == 1]
# [1, 3, 5, 7, 9]

The order matches how you'd write nested for loops: outer loop first, inner loop second.

# Equivalent loop
for row in matrix:
    for n in row:
        if n % 2 == 1:
            result.append(n)

Avoid going beyond two levels of nesting — beyond that, a regular loop is easier to read.


Dict Comprehensions

Same idea, but produces a dict:

# Square each number, keyed by the number
{n: n**2 for n in range(1, 6)}
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# Invert a dict (swap keys and values)
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}

# Filter a dict — keep only high scorers
scores = {"alice": 90, "bob": 55, "carol": 88, "dave": 40}
passing = {name: score for name, score in scores.items() if score >= 70}
# {'alice': 90, 'carol': 88}

# Normalise keys from a raw API response
raw = {"First Name": "Alice", "Last Name": "Smith"}
normalised = {k.lower().replace(" ", "_"): v for k, v in raw.items()}
# {'first_name': 'Alice', 'last_name': 'Smith'}

Set Comprehensions

Use {} (curly braces, no colon) to produce a set — automatically deduplicates:

words = ["apple", "banana", "apricot", "blueberry", "avocado"]

# Unique first letters
first_letters = {w[0] for w in words}
# {'a', 'b'}

# Deduplicate while transforming
numbers = [1, 2, 2, 3, 3, 3, 4]
unique_squares = {n**2 for n in numbers}
# {1, 4, 9, 16}

Generator Expressions

Swap [] for () to get a lazy generator that doesn't build the list in memory:

# Sum without building the full list
total = sum(n**2 for n in range(1_000_000))

# Any/all use short-circuit evaluation with generators
has_even = any(n % 2 == 0 for n in [1, 3, 5, 4, 7])
# True — stops at 4

all_positive = all(n > 0 for n in [1, 2, -1, 4])
# False — stops at -1

# Pass directly to a function — only one pair of parens needed
print(", ".join(str(n) for n in [1, 2, 3]))
# '1, 2, 3'

Use a generator expression when you only need to iterate once or when the dataset is large.


Real-World Patterns

Parse a CSV row

line = "42,hello,3.14,True"
values = [token.strip() for token in line.split(",")]
# ['42', 'hello', '3.14', 'True']

Extract nested values from JSON

users = [
    {"name": "Alice", "active": True},
    {"name": "Bob",   "active": False},
    {"name": "Carol", "active": True},
]
active_names = [u["name"] for u in users if u["active"]]
# ['Alice', 'Carol']

Build a lookup table

words = ["hello", "world", "python"]
length_map = {w: len(w) for w in words}
# {'hello': 5, 'world': 5, 'python': 6}

Transpose a matrix

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
transposed = [[row[i] for row in matrix] for i in range(3)]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

Remove duplicates while preserving order

seen = set()
deduped = [x for x in items if not (x in seen or seen.add(x))]

Comprehension vs Loop: When to Use Each

Use comprehension Use a regular loop
Single transformation Multiple statements per iteration
Result fits in memory Very large datasets (use generator)
Clear and readable Complex nested logic
Building a list/dict/set Side effects only (print, file write)
Performance matters Readability is more important

The rule of thumb: if it doesn't fit on one readable line, write a loop.

# Good — simple and clear
doubles = [x * 2 for x in data]

# Borderline — consider a loop
result = [transform(x) for x in data if predicate(x) and not exclude(x)]

# Too complex — write a loop
result = []
for x in data:
    if predicate(x):
        y = step_one(x)
        z = step_two(y)
        result.append(z)

Performance

List comprehensions are typically 20–35% faster than equivalent for loops because the loop overhead runs in C rather than Python bytecode.

import timeit

# Loop
def with_loop():
    result = []
    for n in range(1000):
        result.append(n * 2)
    return result

# Comprehension
def with_comp():
    return [n * 2 for n in range(1000)]

# timeit shows comprehension wins by ~25%

For very large data you don't need all at once, generator expressions beat both:

# Reads file lazily — constant memory
total = sum(len(line) for line in open("big.txt"))

6 Common Mistakes

Mistake Problem Fix
[x for x in range(10) if x else 0] if at the end filters, doesn't substitute Use x if x else 0 for x in range(10)
Nested comprehension for simple flatten Hard to read Use itertools.chain.from_iterable
Building a huge list only to iterate it Wastes memory Use a generator expression
[d.update({"k": v}) for d in dicts] Side effects in comprehensions Use a regular loop
[[]] * 3 instead of [[] for _ in range(3)] All rows share the same object Always use comprehension for mutable defaults
[... for _ in range(n)] when n is large _ is fine for the variable, but the list may be huge Consider sum(), any(), all(), or a generator

FAQ

Are list comprehensions always faster than loops?
Usually yes for simple transformations, but profile before optimising. map() with a built-in function (e.g., map(str, nums)) can be even faster because it avoids Python-level function calls entirely.

When should I use map() instead?
When applying a single built-in function: list(map(int, strings)) is idiomatic and fast. For custom lambdas, a comprehension is more readable: [int(s) for s in strings].

Can I use walrus operator (:=) in comprehensions?
Yes, in Python 3.8+. Useful to compute a value once and filter on it:

results = [y for x in data if (y := transform(x)) is not None]

What's the difference between [x for x in lst] and lst.copy()?
Both create a shallow copy, but lst.copy() (or lst[:]) is faster and more explicit when copying is the goal.

Can comprehensions replace filter() and map()?
Yes — [f(x) for x in xs] replaces list(map(f, xs)) and [x for x in xs if p(x)] replaces list(filter(p, xs)). Most Pythonistas prefer comprehensions for readability.

How do I debug a comprehension?
Extract it into a loop temporarily, add print() statements, then convert back once it works.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools