That one-line Python syntax that looks backwards until you read it in the right order. Here is the plain-English translation.

List comprehensions are the syntax that makes new Python developers feel they are reading a foreign language: square brackets, a for loop squeezed onto one line, sometimes an if at the end. The good news is that a comprehension is just a normal loop written more compactly. Once you learn to read it in the right order, it stops looking backwards.

By the end you will be able to:

  • Turn any simple for loop that builds a list into a comprehension, and back again.
  • Read a comprehension out loud in the right order.
  • Filter with if, and know how if / else differs.
  • Decide when a plain loop is the better choice.

You need to know what a Python list and a for loop are. Everything below can be tried in a Python shell or a file.

Start with the regular loop

Say you have a list of numbers and want a new list with the square of each. Written the long way, you make an empty list, loop over the numbers, and add each answer to it:

numbers = [1, 2, 3, 4, 5, 6]

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

print(squares)   # [1, 4, 9, 16, 25, 36]
The loop takes each number, changes it, and collects the result in a new list.

Notice the pattern: (1) start with an empty list, (2) loop over something, (3) put something into the list each time. That three-part pattern is exactly what a list comprehension squeezes into one line.

The same thing as a list comprehension

Here is the exact same job as a comprehension:

squares = [n * n for n in numbers]
print(squares)   # [1, 4, 9, 16, 25, 36]
Same three pieces, same colours: the comprehension just puts them on one line.

How to read it

The trick is that you do not read it left to right. Start with the loop, then the expression, then the brackets:

Read the “for” part first. The rest falls into place.

Said in plain English: “For each n in numbers, work out n times n, and put them all in a list.” The general shape is:

[ what_to_keep   for  item  in  something ]

Adding a condition

You can also filter while you build by adding an if at the end. Only the items where the condition is true are used. First the loop version, then the short one:

evens = []
for n in numbers:
    if n % 2 == 0:
        evens.append(n)
evens = [n for n in numbers if n % 2 == 0]
print(evens)     # [2, 4, 6]
The if part is a gate: only items that pass get into the new list.

The reading order is the same: loop through numbers, keep only the ones where the condition is true, and collect them. (n % 2 is the remainder after dividing by 2, so it is 0 for even numbers.)

Change and filter together

You can do both at once. The if decides which items get in, and the expression at the front decides what they look like:

even_squares = [n * n for n in numbers if n % 2 == 0]
print(even_squares)   # [4, 16, 36]

if versus if / else

This is the number one thing that confuses people. There are two different places an if can go, and they do different jobs:

Where the if goesWhat it doesExample
At the end, after the loopFilters: skips items, so the result can be shorter[n for n in numbers if n % 2 == 0]
At the front, with an elseChooses a value for every item, so the length stays the same["even" if n % 2 == 0 else "odd" for n in numbers]
labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
# ['odd', 'even', 'odd', 'even', 'odd', 'even']

More things you can do

The item does not have to be a number. Comprehensions are very handy for cleaning up text:

words = ["  Apple ", "fig", "  Kiwi"]
clean = [w.strip().lower() for w in words]
# ['apple', 'fig', 'kiwi']

You can use two for parts to flatten a list of lists. Read it like two nested loops, outer loop first:

grid = [[1, 2], [3, 4], [5]]
flat = [n for row in grid for n in row]
# [1, 2, 3, 4, 5]

The same idea works with other brackets. Curly braces make a dictionary or a set, and round brackets (inside a function call) make a generator that produces values one at a time without building the whole list in memory:

lengths = {w: len(w) for w in ["apple", "fig", "kiwi"]}   # dict
# {'apple': 5, 'fig': 3, 'kiwi': 4}

remainders = {n % 3 for n in numbers}                    # set
# {0, 1, 2}

total = sum(n * n for n in numbers)                      # generator
# 91

When to use one, and when not to

A comprehension is great when the logic fits comfortably on one line: a simple change, a simple filter, or both. It is not a rule you must follow everywhere. If it makes the code harder to read, use a loop.

Shorter is only better when it is also easier to read.
# This is too much for one line:
result = [transform(x) for x in items if x.ok and x.size > 3 and not x.hidden]

# Clearer as a loop:
result = []
for x in items:
    if not x.ok:
        continue
    if x.size <= 3 or x.hidden:
        continue
    result.append(transform(x))

Common mistakes

What happensWhyFix
SyntaxError on [n for n in numbers else 0]else cannot go at the endMove it to the front: [n if n > 0 else 0 for n in numbers]
The result is shorter than you expectedThe if at the end filters items outUse a if condition else b at the front to keep every item
You get None values in the listYou used a function that returns nothing, such as print, as the expressionPut the value you want in the list at the front
The loop order in a nested one is wrongTwo for parts read like nested loops, outer firstWrite the loops out in full, then copy them across in the same order
Nobody can read itToo much on one lineSwitch to a loop with a clear name for each step

Try it yourself

1. From [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], build a list of the even numbers doubled.

Show answer
[n * 2 for n in range(1, 11) if n % 2 == 0]
# [4, 8, 12, 16, 20]

2. From ["apple", "fig", "banana", "kiwi"], keep the words longer than 3 letters.

Show answer
words = ["apple", "fig", "banana", "kiwi"]
[w for w in words if len(w) > 3]
# ['apple', 'banana', 'kiwi']

3. Rewrite this loop as a comprehension: result = [], then for w in words: result.append(len(w)).

Show answer
result = [len(w) for w in words]