DEV Community

Cover image for Python List Comprehensions: From Loops to One-Liners
German Yamil
German Yamil

Posted on

Python List Comprehensions: From Loops to One-Liners

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)
Enter fullscreen mode Exit fullscreen mode

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)]
Enter fullscreen mode Exit fullscreen mode

Let's build up from zero.


The Basic Syntax

The structure of a list comprehension is:

[expression for item in iterable]
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

After:

upper = [name.upper() for name in names]
# ['ALICE', 'BOB', 'CAROL']
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

Filtering with if

Add a condition at the end to keep only the items you want:

[expression for item in iterable if condition]
Enter fullscreen mode Exit fullscreen mode

Before:

nums = [-3, -1, 0, 2, 5, 8]
positives = []
for x in nums:
    if x > 0:
        positives.append(x)
Enter fullscreen mode Exit fullscreen mode

After:

positives = [x for x in nums if x > 0]
# [2, 5, 8]
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

Before:

nums = [-3, 1, -2, 4, 0]
clipped = []
for x in nums:
    if x > 0:
        clipped.append(x)
    else:
        clipped.append(0)
Enter fullscreen mode Exit fullscreen mode

After:

clipped = [x if x > 0 else 0 for x in nums]
# [0, 1, 0, 4, 0]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

Before:

pairs = []
for x in [1, 2]:
    for y in ["a", "b"]:
        pairs.append((x, y))
Enter fullscreen mode Exit fullscreen mode

After:

pairs = [(x, y) for x in [1, 2] for y in ["a", "b"]]
# [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

Before:

names = ["alice", "bob", "carol"]
name_lengths = {}
for name in names:
    name_lengths[name] = len(name)
Enter fullscreen mode Exit fullscreen mode

After:

name_lengths = {name: len(name) for name in names}
# {'alice': 5, 'bob': 3, 'carol': 5}
Enter fullscreen mode Exit fullscreen mode

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'}
Enter fullscreen mode Exit fullscreen mode

Set Comprehensions

Use curly braces without a colon to get a set (unique values, unordered):

{expression for item in iterable}
Enter fullscreen mode Exit fullscreen mode
words = ["apple", "banana", "apple", "cherry", "banana"]

# Deduplicate and uppercase in one step
unique_upper = {w.upper() for w in words}
# {'APPLE', 'BANANA', 'CHERRY'}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
# 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))
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.


Further Reading

Top comments (0)