Technology  /  Python

🐍 Python 78 guides · updated 2026

From first variable to OOP, generators, and real projects — the language that runs everything from data pipelines to AI agents, taught the practical way.

List Comprehensions in Python: Clean Syntax, Real Performance, Practical Limits

List comprehensions let you build a new list from an iterable in a single expression. They are not just syntactic sugar — CPython compiles them differently from for loops, and the result is measurably faster for most workloads.

Basic Syntax

# General form:
# [expression for item in iterable if condition]
# Basic: squares of 0..9
squares = [x**2 for x in range(10)]
print(squares)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# With condition: only even squares
even_squares = [x**2 for x in range(10) if x % 2 == 0]
print(even_squares)
# [0, 4, 16, 36, 64]
# Transform strings
words = ["hello", "world", "python"]
upper = [w.upper() for w in words]
print(upper)
# ['HELLO', 'WORLD', 'PYTHON']

Comprehension vs for Loop — Performance

Comprehensions are faster because CPython uses the LIST_APPEND bytecode opcode instead of calling list.append() as a method lookup on each iteration. The difference is typically 20-35% for simple cases.

import timeit
# For loop version
def squares_loop(n):
result = []
for x in range(n):
result.append(x**2)
return result
# Comprehension version
def squares_comp(n):
return [x**2 for x in range(n)]
n = 100_000
loop_time = timeit.timeit(lambda: squares_loop(n), number=100)
comp_time = timeit.timeit(lambda: squares_comp(n), number=100)
print(f"Loop: {loop_time:.3f}s")
print(f"Comprehension: {comp_time:.3f}s")
# Comprehension is typically faster

Conditional Expressions (if-else in the Expression)

The if condition at the end filters elements. An if-else in the expression position transforms them:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Filter: keep only evens
evens = [x for x in numbers if x % 2 == 0]
print(evens) # [2, 4, 6, 8, 10]
# Transform: label each number as even or odd
labels = ["even" if x % 2 == 0 else "odd" for x in numbers]
print(labels)
# ['odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even']
# Both: filter and transform
big_evens = [x * 10 for x in numbers if x > 5 and x % 2 == 0]
print(big_evens) # [60, 80, 100]

Nested Comprehensions

Nested comprehensions flatten nested structures or generate cartesian products:

# Flatten a 2D list
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [item for row in matrix for item in row]
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Cartesian product
sizes = ["S", "M", "L"]
colours = ["red", "blue"]
variants = [f"{s}-{c}" for s in sizes for c in colours]
print(variants)
# ['S-red', 'S-blue', 'M-red', 'M-blue', 'L-red', 'L-blue']
# Generate a 3x3 identity matrix
identity = [[1 if i == j else 0 for j in range(3)] for i in range(3)]
for row in identity:
print(row)
# [1, 0, 0]
# [0, 1, 0]
# [0, 0, 1]

The order of for clauses in a comprehension matches the order they would appear in nested for loops — outermost first.

Set and Dict Comprehensions

The same syntax works for sets and dicts:

# Set comprehension — unique values only
words = ["cat", "dog", "cat", "bird", "dog"]
unique_lengths = {len(w) for w in words}
print(unique_lengths) # {3, 4} (cat/dog=3, bird=4)
# Dict comprehension
names = ["Alice", "Bob", "Carol"]
name_lengths = {name: len(name) for name in names}
print(name_lengths) # {'Alice': 5, 'Bob': 3, 'Carol': 5}
# Invert a dict
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
print(inverted) # {1: 'a', 2: 'b', 3: 'c'}

Where the Readability Limit Is

Comprehensions have a readability limit. A good heuristic: if it does not fit comfortably on two lines, rewrite it as a loop.

# Readable — one condition, simple expression
result = [x**2 for x in range(100) if x % 3 == 0]
# Getting harder to read — two loops and a condition
flat_filtered = [item for row in matrix for item in row if item > 3]
# Too complex — use a regular loop
# Bad:
result = [f(x) for x in data if condition1(x) and condition2(x) and x not in seen]
# Good:
result = []
for x in data:
if condition1(x) and condition2(x) and x not in seen:
result.append(f(x))

Generator Expression vs List Comprehension

If you only need to iterate the result once, use a generator expression (round brackets instead of square) — it produces values lazily and uses far less memory:

# List comprehension — builds the full list in memory
total = sum([x**2 for x in range(1_000_000)])
# Generator expression — computes one value at a time
total = sum(x**2 for x in range(1_000_000)) # no brackets needed inside sum()
# Memory comparison
import sys
list_comp = [x**2 for x in range(1000)]
gen_exp = (x**2 for x in range(1000))
print(sys.getsizeof(list_comp)) # ~8856 bytes
print(sys.getsizeof(gen_exp)) # ~112 bytes (just the generator object)

Use a list comprehension when you need the full list (multiple iterations, indexing, len()). Use a generator expression when you are feeding the result directly into sum(), max(), any(), all(), or another single-pass consumer.