List Comprehensions

A list comprehension builds a new list from an existing iterable in a single, readable expression: "the square of each number", "every name that starts with A". They're one of Python's most loved features and replace many short for loops.

From loop to comprehension

PY basic.py
numbers = [1, 2, 3, 4, 5]

squares = []
for n in numbers:
    squares.append(n * n)
print(squares)

squares = [n * n for n in numbers]
print(squares)
Output
[1, 4, 9, 16, 25]
[1, 4, 9, 16, 25]

Read [n * n for n in numbers] as "n * n for each n in numbers". The expression comes first, then the loop.

Filtering with if

PY filter.py
words = ["apple", "Banana", "avocado", "cherry", "Apricot"]

a_words = [w for w in words if w.lower().startswith("a")]
print(a_words)

long_upper = [w.upper() for w in words if len(w) > 6]
print(long_upper)
Output
['apple', 'avocado', 'Apricot']
['AVOCADO', 'APRICOT']

An if at the end keeps only the items that pass the test.

Choosing a value with if/else

To transform every item but pick between two results, put a conditional expression at the front:

PY if_else.py
scores = [91, 45, 78, 30]

results = ["pass" if s >= 50 else "fail" for s in scores]
print(results)

clipped = [min(max(s, 40), 90) for s in scores]
print(clipped)
Output
['pass', 'fail', 'pass', 'fail']
[90, 45, 78, 40]

Remember the difference: if at the end filters (fewer items); … if … else … at the front chooses a value (same number of items).

Nested loops

PY nested.py
colors = ["red", "blue"]
sizes = ["S", "M"]

variants = [f"{c}-{s}" for c in colors for s in sizes]
print(variants)

matrix = [[1, 2, 3], [4, 5, 6]]
flat = [n for row in matrix for n in row]
print(flat)

transposed = [[row[i] for row in matrix] for i in range(3)]
print(transposed)
Output
['red-S', 'red-M', 'blue-S', 'blue-M']
[1, 2, 3, 4, 5, 6]
[[1, 4], [2, 5], [3, 6]]

Multiple for clauses run in the same order as nested loops would, outer first. Beyond two levels, a regular loop is usually clearer.

Dictionary and set comprehensions

The same syntax with braces builds dictionaries and sets:

PY dict_set.py
names = ["ana", "ben", "cleo", "ana"]

lengths = {name: len(name) for name in names}
print(lengths)

unique_upper = {name.upper() for name in names}
print(sorted(unique_upper))

prices = {"tea": 3.0, "cake": 4.5, "juice": 5.0}
discounted = {item: round(p * 0.9, 2) for item, p in prices.items() if p > 4}
print(discounted)
Output
{'ana': 3, 'ben': 3, 'cleo': 4}
['ANA', 'BEN', 'CLEO']
{'cake': 4.05, 'juice': 4.5}

Generator expressions

Using parentheses instead of brackets creates a generator, which produces items one at a time instead of building a whole list. Pass one straight into functions like sum, max, any or "".join:

PY generator.py
numbers = range(1, 1_000_001)

total = sum(n * n for n in numbers)
print(total)

print(any(n % 7 == 0 for n in [3, 5, 14]))
print(", ".join(str(n) for n in range(5)))
Output
333333833333500000
True
0, 1, 2, 3, 4

The sum above never stores a million squares in memory. Generators are covered properly in lesson 35.

When not to use a comprehension

  • When you're doing something for its side effect (printing, saving to a file) rather than building a list — use a normal loop.
  • When the logic needs several statements, error handling or deep nesting — a loop with clear steps reads better.
  • When the line gets so long you have to scroll — break it up or use a loop.

Comprehensions should make code clearer. If one needs a comment to explain it, it's probably too clever.

Variables don't leak

PY scope.py
x = "outer"
squares = [x * x for x in range(3)]
print(squares, x)
Output
[0, 1, 4] outer

The loop variable inside a comprehension has its own scope, so it doesn't overwrite a variable of the same name outside.

Try it yourself

Try it yourself
Console output