squares = [x * x for x in range(10)]
evens_only = [x for x in range(10) if x % 2 == 0]
squared_dict = {x: x * x for x in range(5)}
unique_lengths = {len(w) for w in ["cat", "dog", "elephant"]} # a set comprehension
A list comprehension isn't purely cosmetic — it's generally faster than the equivalent for loop with .append(), because the looping happens in optimized C code inside the interpreter rather than as interpreted Python bytecode repeatedly calling .append() on every iteration. For anything beyond a simple filter/transform, though, a comprehension that needs nested loops or complex conditions usually reads worse than a plain loop — readability should still win once it gets that complex.
matrix = [[1, 2, 3], [4, 5, 6]]
flattened = [x for row in matrix for x in row] # [1, 2, 3, 4, 5, 6]
The clauses read left to right in the same order you'd write the equivalent nested for loops — for row in matrix is the outer loop, for x in row is the inner one. Read it as: "for each row in matrix, for each x in that row, collect x." Getting the order backwards is a genuinely common early mistake.
These look similar but do different things — mixing them up is a common source of confusion:
# Filter clause — trailing "if" with no "else" DROPS non-matching items
[x for x in range(10) if x % 2 == 0] # [0, 2, 4, 6, 8]
# Conditional expression — "if/else" BEFORE the loop TRANSFORMS every item
[x if x % 2 == 0 else -1 for x in range(10)] # [0, -1, 2, -1, 4, -1, 6, -1, 8, -1]
A trailing if with no else filters the list down; an if/else placed before the for keeps every item but changes its value.