Skip to main content

Comprehensions

The loop that makes an empty list and appends to it, written as one expression. Build a list comprehension part by part (output expression, for clause, if clause), then dictionary and set comprehensions, and learn when a plain loop is clearer.
P01.10.1L0 Foundation25 minIn review

Before this lesson

P01.6 Lists, tuples, dictionaries and sets P01.9 Objects and classes

After this lesson you can

  • Rewrite a loop that appends to an empty list as a list comprehension, and back again.
  • Name the three parts of a comprehension (output expression, for clause, optional if clause) and say what each one does.
  • Build dictionaries and sets with comprehensions: the score of each name, and the set of bands.
  • Decide when a plain loop is clearer than a comprehension: side effects, or more than two clauses.

In review. The maths and code on this page are checked by automated tests on every change, but an independent reader has not signed it off yet. Spot a mistake or a confusing step? Tell us.

The question

You have written these four lines many times: make an empty list, loop over the scores, test each one, append the ones that pass. The sentence they stand for is short: “the scores that are at least 50”. Can the code be as short as the sentence, and still be read from left to right?

Intuition

Most loops that build a list have the same shape. An empty list is made, a loop walks over another list, an if decides which items to keep, and append adds something made from each kept item. Only three things change from one such loop to the next: what is appended, what is looped over, and the test.

A comprehension writes those three things, and nothing else, inside one pair of brackets.

Top: the four-line loop passes = [], for s in scores:, if s >= 50:, passes.append(s), with the three lines that change marked 1, 2 and 3. Beside it the result, passes, drawn as four numbered slots holding 55, 65, 80 and 60. Bottom: the same list built by one line, passes = [s for s in scores if s >= 50]. Brackets under its parts carry the same numbers: 1 under the first s, the output expression; 2 under for s in scores, the for clause; 3 under if s >= 50, the if clause, which is optional.
P01.10.1-F1

One loop, one expression. Each numbered part of the comprehension is one line of the loop, read in a new order: what to keep first, then where it comes from, then the test.

The parts keep their order except for one. The thing appended, which comes last in the loop, moves to the front of the comprehension. So the line reads almost like the sentence: “s, for each s in scores, if s >= 50”.

Build it up

The study table gives the running data, as in every lesson of this course.

names = ['Ama', 'Ben', 'Chen', 'Dina', 'Eli']
hours = [2, 4, 5, 1, 3]
scores = [55, 65, 80, 40, 60]

passes = []
for s in scores:
    if s >= 50:
        passes.append(s)
print(passes)
[55, 65, 80, 60]

That is the loop. Now build the comprehension one part at a time.

Step 1. Output expression and for clause. The smallest comprehension has two parts: an expression, then a for clause. [s for s in scores] makes a new list holding every score: a copy.

Step 2. Add an if clause. An if at the end keeps only the items for which the test is True. [s for s in scores if s >= 50] drops 40, the one score below the pass mark.

Step 3. Change the output expression. The expression at the front can be any expression that uses the name. [band_of(s) for s in scores] gives the band of each score, with the function of P01.6.3.

The five scores 55, 65, 80, 40, 60 as numbered slots at the top. Below them three steps, each a line of code above the list it makes. Step 1, [s for s in scores]: the same five numbers, a copy. Step 2, [s for s in scores if s >= 50]: four slots 55, 65, 80, 60, with a note that 40 >= 50 is False so 40 is dropped. Step 3, [band_of(s) for s in scores]: five slots holding 'pass', 'pass', 'distinction', 'fail', 'pass'.
P01.10.1-F2

Three steps. The for clause alone copies; an if clause filters; a new output expression transforms each item.

def band_of(score):
    if score >= 70:
        return 'distinction'
    elif score >= 50:
        return 'pass'
    else:
        return 'fail'


print([s for s in scores])
print([s for s in scores if s >= 50])
print([band_of(s) for s in scores])
[55, 65, 80, 40, 60]
[55, 65, 80, 60]
['pass', 'pass', 'distinction', 'fail', 'pass']

The output expression can do arithmetic too. The running function predict of this course turns every number of hours into a predicted score.

def predict(hours):
    return 9 * hours + 33


print([predict(h) for h in hours])
[51, 69, 78, 42, 60]

The formal statement

NoteList comprehension

A list comprehension is an expression in square brackets: an output expression, then a for clause, then zero or more further for or if clauses.

[expression for name in iterable if condition]

It makes a new list. It means exactly this loop, run on the spot:

result = []
for name in iterable:
    if condition:
        result.append(expression)

The Python Tutorial (section 5.1.3) defines it in the same words: “brackets containing an expression followed by a for clause, then zero or more for or if clauses”. Three rules follow from the loop above.

  • The output expression is worked out once for each item that passes every test, in the order of the items. So the new list has as many items as pass, never more.
  • The name after for is new and belongs to the comprehension. After the line has run, the name s of [s for s in scores] is not left behind, and a name s outside the comprehension, if there is one, keeps its old value.
  • The original list is not changed. A comprehension always makes a new one.

Dictionaries and sets. The same idea works in curly brackets. A dictionary comprehension has a key and a value with a colon between them; a set comprehension has one expression.

{key: value for name in iterable}
{expression for name in iterable}
Two parts, one above the other. Top: the dictionary comprehension {name: score for name, score in zip(names, scores)} above a two-column table that maps Ama to 55, Ben to 65, Chen to 80, Dina to 40 and Eli to 60. Bottom: the set comprehension {band_of(s) for s in scores}. Five bands go in, pass, pass, distinction, fail, pass, and the set holds only three, distinction, fail and pass, drawn loose inside a rounded outline with a note that a set keeps one of each and has no positions.
P01.10.1-F4

A dictionary comprehension makes key-value pairs; a set comprehension makes a set, so repeats collapse to one.

The dictionary maps each name to a score, with zip from P01.6.2 handing over the pairs. The set holds each band once: five bands go in and three come out, because a set keeps one of each (P01.6.3). A set has no positions, so the order in which Python prints it is not something to rely on.

Worked example

Work out [s for s in scores if s >= 50] by hand, then the mean of the passes. The trace table has one row for each item of scores.

A trace table with the columns item, s, s >= 50 and the list so far. Item 0: s is 55, True, the list is [55]. Item 1: 65, True, [55, 65]. Item 2: 80, True, [55, 65, 80]. Item 3: 40, False, shaded, the list stays [55, 65, 80]. Item 4: 60, True, [55, 65, 80, 60]. Below: four passes, total 260, mean 260 / 4 = 65.0.
P01.10.1-F3

The trace of the comprehension. Only the row whose test is False leaves the list as it was.

  • Item 0, counting from 0: s is 55. The test \({55 \ge 50}\) is True, so 55 is added.
  • Items 1 and 2: 65 and 80 pass, and the list becomes [55, 65, 80].
  • Item 3: 40. The test \({40 \ge 50}\) is False, so nothing is added.
  • Item 4: 60 passes. The result is [55, 65, 80, 60].

Four students passed. Their scores add up to \({55 + 65 + 80 + 60} = 260\), so the mean of the passes is \({260 / 4} = 65.0\), the number this course has met before.

Code

The worked example, then the mean of the passes and the two curly-bracket forms.

passes = [s for s in scores if s >= 50]
print(passes, len(passes), sum(passes) / len(passes))

score_of = {name: score for name, score in zip(names, scores)}
print(score_of)
print(score_of['Chen'])

bands = {band_of(s) for s in scores}
print(len(bands), sorted(bands))
[55, 65, 80, 60] 4 65.0
{'Ama': 55, 'Ben': 65, 'Chen': 80, 'Dina': 40, 'Eli': 60}
80
3 ['distinction', 'fail', 'pass']

The set is printed through sorted, which turns it into a list in alphabetical order, so that the output is the same on every run.

A comprehension can also build the numbers machine learning cares about. The errors of predict are the scores minus the predictions, and their mean square is the mean squared error of the course, 8.

errors = [s - predict(h) for h, s in zip(hours, scores)]
squares = [e * e for e in errors]
print(errors)
print(sum(squares) / len(squares))
[4, -4, 2, -2, 0]
8.0

The loop variable of a for statement is left behind with its last value, while the name inside a comprehension is not. That difference rarely matters, but it is one reason comprehensions are tidy.

Explore

This module’s interactive, the lazy and eager pipeline, lives in P01.10.3. Its eager mode is made of the list comprehensions of this lesson: each one builds its whole list before the next one starts. Count how many items are held in lists by the end; lesson P01.10.3 shows how to hold far fewer.

Pitfalls

Wrong, with a cross: the line [print(s) for s in scores]. It prints the five scores, but it also builds a list of five slots that each hold None, which is thrown away; a note says a comprehension is for making a list, not for doing something. Right, with a tick: a plain loop, for s in scores: print(s). It prints the same five lines and builds nothing.
P01.10.1-F5

Wrong beside right. A comprehension used only for what it does along the way builds a list of None for nothing; a loop says what is meant.

“A comprehension is just a shorter loop, so I can use it for anything a loop does.”

A comprehension is for making a list. [print(s) for s in scores] prints the five scores, but it also builds a list of five None values, because print returns None, and then throws it away. When the point of a loop is what it does along the way (printing, writing to a file, changing another object), write a plain for loop.

“If two clauses are good, three are better.”

[s for row in table for s in row if s >= 50] is legal, and it takes the passing scores out of a list of lists. Few readers can follow it at a glance. With more than two clauses, or with a test that needs elif, a plain loop with good names is clearer, and clear code is code you can debug.

“The comprehension changed my list.”

It never does. [s for s in scores if s >= 50] makes a new list and leaves scores with all five items. To keep the result, give it a name.

Check yourself

Recall

1. What are the three parts of [s for s in scores if s >= 50]?

Show answer

The output expression s, the for clause for s in scores, and the if clause if s >= 50.

2. Write the loop that [predict(h) for h in hours] stands for.

Show answer

result = [], then for h in hours: with the body result.append(predict(h)).

3. How many items does [s for s in scores if s >= 50] have, and why?

Show answer

Four: one for each score that passes the test. Only 40 fails it.

4. What is the difference between {band_of(s) for s in scores} and [band_of(s) for s in scores]?

Show answer

The first is a set, with each band once (three items, no positions); the second is a list with one band per score (five items, in order).

5. Name two cases in which a plain loop is clearer than a comprehension.

Show answer

When the loop is there for its side effects, such as printing; and when there are more than two clauses.

Exercises

1. ★ Write a comprehension for the names of the students who studied at least 3 hours, using zip(names, hours). Work out its value by hand.

Show solution

[name for name, h in zip(names, hours) if h >= 3]. The pairs are (Ama, 2), (Ben, 4), (Chen, 5), (Dina, 1) and (Eli, 3). The hours 4, 5 and 3 pass, so the value is ['Ben', 'Chen', 'Eli'].

2. ★★ Write a dictionary comprehension that maps each name to its band, and say how many keys map to 'pass'.

Show solution

{name: band_of(score) for name, score in zip(names, scores)} gives Ama, Ben and Eli 'pass', Chen 'distinction' and Dina 'fail'. Three keys map to 'pass', as in the tally of P01.3.1.

3. ★★★ Rewrite this loop as one comprehension, then check that both give the same list by hand.

gaps = []
for h, s in zip(hours, scores):
    if s >= 50:
        gaps.append(s - predict(h))
Show solution

gaps = [s - predict(h) for h, s in zip(hours, scores) if s >= 50]. The output expression is the thing appended, the for clause is the loop header, and the if clause is the test. By hand: the errors are 4, -4, 2, -2 and 0, and Dina’s row, with score 40, fails the test. So both give [4, -4, 2, 0].

Summary card

A comprehension is the loop-and-append pattern written as one expression.

The anchor figure again: the four-line loop that appends the passing scores, its three changing lines numbered 1 to 3, and the one-line comprehension [s for s in scores if s >= 50] with the same numbers under its output expression, for clause and if clause.
P01.10.1-F1

Loop and comprehension, part by part.

  • List: [expression for name in iterable if condition], a new list.
  • Dictionary: {key: value for name in iterable}; set: {expression for name in iterable}.
  • Order: the output expression comes first, but it is worked out last, for each item that passes.
  • Use a loop instead for side effects, or when there would be more than two clauses.

Sources and next steps

Sources. Each was opened and checked against this lesson on 12 November 2026.

  • Python Software Foundation, The Python Tutorial, chapter 5, Data Structures. Section 5.1.3 defines a list comprehension as “brackets containing an expression followed by a for clause, then zero or more for or if clauses” and gives the equivalent loop; section 5.4 shows set comprehensions and section 5.5 dictionary comprehensions.
  • Python Software Foundation, Functional Programming HOWTO, section “Generator expressions and list comprehensions”: a list comprehension returns a list, and its general form with several for and if clauses.
  • Barry Warsaw, PEP 202, List Comprehensions: the proposal for Python 2.0 that added the form as “a more concise way to create lists”, whose clauses “nest in the same way for loops and if statements nest now”.

Next. P01.10.2 Iterables and iterators opens up the for clause and shows what a loop really asks of the list it walks over.

This lesson unlocks

P01.10.2 Iterables and iterators

Your progress is saved in this browser only. No account, no tracking.
Back to top