PythonBeginner

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.

Numbers 1 to 4 flow through n * n and become the list 1, 4, 9, 16
On this page

If you've read other people's Python code, you've almost certainly seen something like this:

Python
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:

Python
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:

Python
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:

Text
[expression for item in iterable if condition]
[] n * nexpressionwhat to collect for n in range(10)for clausewhere items come from if n % 2 == 0if clause (optional)which items to keep result → [0, 4, 16, 36, 64]
The same three parts in a real comprehension that squares the even numbers below 10
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:

Python
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:

Python
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:

Python
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:

Python
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:

Python
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:

Python
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:

Python
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:

Python
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:

Python
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:

Python
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 of None values. Use a for loop.
  • You need try/except or several statements per item. A comprehension holds a single expression; a loop or a helper function is clearer.
Python
# 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.