Creating Lists Efficiently

A list comprehension is a compact expression that builds a list in one line. The syntax [expr for x in it if cond] combines three steps — iterate, filter, transform — into a readable whole. For the common case of “take these items, keep the ones that match, apply this function”, a comprehension is the shortest and clearest code Python can offer.

Compared to the equivalent for-loop, a comprehension is both faster (the interpreter knows it is building a list and can optimize) and less error-prone (no append, no intermediate variable, no forgotten result = [] on a second call). The readability benefit is the main reason to reach for it, though; the performance is a welcome side effect.

Two features make comprehensions surprisingly powerful. First, the if clause is optional and can appear more than once: [x for x in nums if x > 0 if x % 2 == 0] keeps only positive even numbers. Second, you can nest for clauses: [(a, b) for a in xs for b in ys] produces every pair, exactly like a nested loop. Both forms read left-to-right, top-to-bottom.

The same syntax extends to dict, set, and generator comprehensions. Wrap in {} with a : and you get a dict comprehension; omit the colon and you get a set comprehension; wrap in () and you get a generator expression that yields items lazily. Mastering list comprehensions is the gateway to all three.

The three roles of a comprehension

Transform: [f(x) for x in xs]. Same length as the input, each element replaced. Filter: [x for x in xs if cond(x)]. Shorter than the input, same shape. Both: [f(x) for x in xs if cond(x)]. Think of it as SQL's SELECT and WHERE in one expression.

The expr on the left can be arbitrarily complex: a function call, a ternary (x if x > 0 else 0), a tuple. Keep it readable; if the body starts to need its own explanation, switch back to a plain loop.

When not to use a comprehension

Comprehensions are a bad fit when the body has side effects (print, db.save) or when the number of if and for clauses pushes past three. In those cases, a regular for loop is clearer.

Avoid deep nesting inside the expr; a list of lists is usually easier to read via two comprehensions or a nested-for, not one mammoth expression.

Comprehension-style building blocks.

ToolPurpose
[e for x in it]
syntax
Transforms every element of it into a new list.
[e for x in it if c]
syntax
Filters and transforms in one step.
(e for x in it)
syntax
Generator expression (lazy).
{k: v for x in it}
syntax
Dict comprehension.
{e for x in it}
syntax
Set comprehension.
map(f, it)
built-in
Older functional equivalent of [f(x) for x in it].
filter(pred, it)
built-in
Older functional equivalent of a filter-only comprehension.
itertools.chain
function
Flattens multiple iterables into one stream.

Creating Lists Efficiently code example

The script below processes a small dataset with comprehensions, compares it to a for-loop equivalent, and shows a nested case.

# Lesson: Creating Lists Efficiently
import time


nums = list(range(1, 21))

# Transform
squares = [n * n for n in nums]
print("squares:", squares[:5], "...")

# Filter
positives = [n for n in range(-5, 6) if n > 0]
print("positives:", positives)

# Both, with ternary in the body
signs = ["+" if n > 0 else ("0" if n == 0 else "-") for n in range(-2, 3)]
print("signs:", signs)

# Nested: every pair where a < b
pairs = [(a, b) for a in range(4) for b in range(4) if a < b]
print("pairs:", pairs)

# Comprehension vs manual for-loop (timing)
start = time.perf_counter()
manual: list[int] = []
for n in range(10_000):
    if n % 3 == 0:
        manual.append(n * n)
t_manual = time.perf_counter() - start

start = time.perf_counter()
comp = [n * n for n in range(10_000) if n % 3 == 0]
t_comp = time.perf_counter() - start

assert manual == comp
print(f"manual: {t_manual*1000:.2f} ms")
print(f"comp:   {t_comp*1000:.2f} ms")

Read the sample outputs alongside the code:

1) Transform and filter are one-liners that read like natural language.
2) The ternary inside the expression handles a three-way label cleanly.
3) Nested `for` clauses are strictly ordered left-to-right: outer first, inner next.
4) Comprehensions are measurably faster than append-in-loop on large inputs.

Practice converting a for-loop into a comprehension.

names = ["Ana", "ben", " Cai ", "dev"]
# Before:
cleaned = []
for n in names:
    s = n.strip().lower()
    if s:
        cleaned.append(s)

# After:
cleaned2 = [n.strip().lower() for n in names if n.strip()]
assert cleaned == cleaned2
print(cleaned2)

# Dict comprehension: name -> length
lens = {n.strip(): len(n.strip()) for n in names if n.strip()}
print(lens)

Verify the meaning of each form.

assert [n*n for n in range(4)] == [0, 1, 4, 9]
assert [n for n in range(10) if n % 2 == 0] == [0, 2, 4, 6, 8]
assert {n % 3 for n in range(10)} == {0, 1, 2}
assert {n: n*n for n in range(3)} == {0: 0, 1: 1, 2: 4}

Running prints something like:

squares: [1, 4, 9, 16, 25] ...
positives: [1, 2, 3, 4, 5]
signs: ['-', '-', '0', '+', '+']
pairs: [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
manual: 1.08 ms
comp:   0.42 ms