Skip to content
aviral gupta

// B4.2 · ~25 min · Beginner

List comprehensions

After this lesson you can turn a list-building loop into a comprehension, read comprehensions with two for clauses or a comprehension inside another, and remove items with del.

Lesson 2 of 6 in B4 Data structures

You will be able to

  • Write a list comprehension with an if filter in place of a loop that appends
  • Read comprehensions with two for clauses and a comprehension inside another
  • Remove items by position or slice with del, and tell del from pop() and remove()
  1. Warm-up · Activity 1 of 7

    Warm-up from lesson B4.1: nums = [3, 1, 2]. Which of these calls return None? Pick all that apply.

    Select all that apply.

  2. Predict · Activity 2 of 7

    Predict before you read on: what does this print?

    print([x * x for x in range(5) if x % 2 == 0])
  3. Practice · Activity 3 of 7

    Fill in the keyword that keeps only the even numbers.

    nums = [4, 7, 10, 13]
    evens = [n for n in nums ____ n % 2 == 0]
    evens = [n for n in nums n % 2 == 0]
  4. Practice · Activity 4 of 7

    Two for clauses. What does this print?

    print([x + y for x in "ab" for y in "12"])
  5. Practice · Activity 5 of 7

    Put the lines in order to build the same list as this comprehension with a for loop.

    result = [w.upper() for w in words if w]
    1. 1. result.append(w.upper())
    2. 2. if w:
    3. 3.result = []
    4. 4.for w in words:
  6. Brain teaser · Activity 6 of 7

    Brain teaser. Both comprehensions use if. What does this print?

    nums = [3, -1, 2]
    print([x if x > 0 else 0 for x in nums], [x for x in nums if x > 0])
  7. Apply · Activity 7 of 7

    Mini-task. Write flatten(matrix), which returns all values of a list of lists in one flat list, and transpose(matrix), which turns rows into columns. Use one comprehension in each. For [[1, 2, 3], [4, 5, 6]] you should get [1, 2, 3, 4, 5, 6] and [[1, 4], [2, 5], [3, 6]].

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

Cleaning survey answers

A survey collected free-text answers with stray spaces, capitals and blanks. One comprehension tidies them and drops the empty ones: strip() removes spaces at both ends, lower() makes the text lower case. A second comprehension keeps only the yes answers. Then a comprehension inside a comprehension builds a small multiplication table, and del removes answers by position and by slice. Change the answers and run it again.

main.py

answers = ["  Yes", "no ", "", "YES", " maybe", "No"]

# Tidy every answer and drop the empty ones.
cleaned = [a.strip().lower() for a in answers if a.strip()]
print("Cleaned:", cleaned)

yes_votes = [a for a in cleaned if a == "yes"]
print("Yes votes:", len(yes_votes))

# A comprehension inside a comprehension: one inner list per row.
table = [[row * col for col in range(1, 4)] for row in range(1, 4)]
print("Table:", table)

# del removes by position or by slice, and returns nothing.
del cleaned[0]
print("Without the first:", cleaned)
del cleaned[1:3]
print("Without two more:", cleaned)

Run it with

python main.py

Output

Cleaned: ['yes', 'no', 'yes', 'maybe', 'no']
Yes votes: 2
Table: [[1, 2, 3], [2, 4, 6], [3, 6, 9]]
Without the first: ['no', 'yes', 'maybe', 'no']
Without two more: ['no', 'no']
  • The filter a.strip() drops "", because an empty string is false.
  • The table has one inner list per row: the outer comprehension runs the inner one three times.
  • del cleaned[1:3] removed the items at positions 1 and 2, not 3.
Change it and run it

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Exercises

Exercise 1 of 2

Shout the long words

shout_long(words, min_len) should return, in capitals and in their original order, only the words that have at least min_len letters. The starter capitalises every word. Add the filter, keeping it one list comprehension.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Hints
  1. Hint 1

    A filter goes at the end of the comprehension: … for w in words if condition.

  2. Hint 2

    len(w) gives the number of letters. "At least" means >=.

  3. Hint 3

    return [w.upper() for w in words if len(w) >= min_len]

Show a solution

One way to solve it. Yours can look different and still pass the checks.

def shout_long(words: list[str], min_len: int) -> list[str]:
    """Return the words with at least min_len letters, in capitals."""
    return [w.upper() for w in words if len(w) >= min_len]
Run it on your computer

Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.py

def shout_long(words: list[str], min_len: int) -> list[str]:
    """Return the words with at least min_len letters, in capitals."""
    return [w.upper() for w in words]

test_main.py

from main import shout_long


def test_filters_short_words():
    """Keeps only words with at least min_len letters, in capitals"""
    got = shout_long(["hi", "hello", "hey", "world"], 4)
    assert got == ["HELLO", "WORLD"], f"shout_long([...], 4) returned {got!r}, expected ['HELLO', 'WORLD']"


def test_boundary():
    """A word of exactly min_len letters is kept"""
    got = shout_long(["tea", "to"], 3)
    assert got == ["TEA"], f"shout_long(['tea', 'to'], 3) returned {got!r}, expected ['TEA']"


def test_empty():
    """An empty list gives an empty list"""
    got = shout_long([], 2)
    assert got == [], f"shout_long([], 2) returned {got!r}, expected []"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Exercise 2 of 2

Seat labels

seat_labels(rows, cols) should return one list per row of a theatre, with labels A1, A2, … for the first row, B1, B2, … for the second, and so on. seat_labels(2, 3) is [["A1", "A2", "A3"], ["B1", "B2", "B3"]]. The starter uses two for clauses and gets one flat list. Rewrite it as a comprehension inside a comprehension.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.

Hints
  1. Hint 1

    The inner comprehension builds one row: [letter + str(number) for number in range(1, cols + 1)].

  2. Hint 2

    The outer comprehension runs the inner one once per letter in "ABCDEFGHIJ"[:rows].

  3. Hint 3

    return [[letter + str(number) for number in range(1, cols + 1)] for letter in "ABCDEFGHIJ"[:rows]]

Show a solution

One way to solve it. Yours can look different and still pass the checks.

def seat_labels(rows: int, cols: int) -> list[list[str]]:
    """Return one list of seat labels per row: A1, A2, ... then B1, B2, ..."""
    return [[letter + str(number) for number in range(1, cols + 1)] for letter in "ABCDEFGHIJ"[:rows]]
Run it on your computer

Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.py

def seat_labels(rows: int, cols: int) -> list[list[str]]:
    """Return one list of seat labels per row: A1, A2, ... then B1, B2, ..."""
    # This builds one flat list. Build one inner list per row instead.
    return [letter + str(number) for letter in "ABCDEFGHIJ"[:rows] for number in range(1, cols + 1)]

test_main.py

from main import seat_labels


def test_two_rows():
    """Two rows of three seats"""
    got = seat_labels(2, 3)
    assert got == [["A1", "A2", "A3"], ["B1", "B2", "B3"]], f"seat_labels(2, 3) returned {got!r}"


def test_single_seat():
    """One row of one seat is [['A1']]"""
    got = seat_labels(1, 1)
    assert got == [["A1"]], f"seat_labels(1, 1) returned {got!r}, expected [['A1']]"


def test_third_row():
    """The third row starts with C"""
    got = seat_labels(3, 2)
    assert len(got) == 3 and got[2] == ["C1", "C2"], f"seat_labels(3, 2) returned {got!r}; the third row should be ['C1', 'C2']"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Common mistakes

A tuple without parentheses

pairs = [x, x * 2 for x in range(3)]

What Python prints

SyntaxError: did you forget parentheses around the comprehension target?

Why, and the fix

When the expression at the front is a tuple, it must be in parentheses: [(x, x * 2) for x in range(3)]. Without them Python cannot tell where the expression ends, and its message suggests the fix.

if at the front without else

nums = [3, -1, 2]
print([x if x > 0 for x in nums])

What Python prints

SyntaxError: expected 'else' after 'if' expression

Why, and the fix

An if before the for is a conditional expression, which must say what to produce in both cases: [x if x > 0 else 0 for x in nums]. To drop items instead, move the if to the end: [x for x in nums if x > 0].

del past the end of the list

names = ["ana", "ben"]
del names[2]

What Python prints

IndexError: list assignment index out of range

Why, and the fix

names has positions 0 and 1, so there is nothing at position 2. Check the length first, or use del names[-1] for the last item. A slice is forgiving: del names[2:] removes nothing and raises no error.

Python in the browser: Pyodide 314.0.7, MPL-2.0. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

A loop in one expression

[expression for item in sequence if condition] builds a new list: for each item, if the condition is true, the value of the expression goes in. It does the same as creating an empty list, looping, and appending, but in one readable line. The loop variable belongs to the comprehension, so it does not overwrite a variable of the same name outside, unlike a for loop. The if at the end filters. To choose between two values instead, put a conditional expression at the front: [x if x > 0 else 0 for x in nums].

Two for clauses, or one comprehension inside another

A comprehension can have several for clauses. They run in the order written, like nested loops: [v for row in grid for v in row] reads "for each row, for each v in that row" and gives one flat list. A comprehension can also be the expression of another one: [[r * c for c in range(3)] for r in range(3)] builds one inner list per r, so the result is a list of lists. If the expression is a tuple, it needs parentheses.

del removes by position

del a[i] removes the item at position i; del a[i:j] removes a slice; del a[:] empties the list, which stays the same list object. del is a statement, not a method, so it returns nothing: result = del a[0] is a SyntaxError. Use pop(i) when you need the removed value, and remove(x) when you know the value but not its position. del name deletes the variable itself.

Sources

Last reviewed September 29, 2026