RizTech Academy logo
RizTech Academy
Data StructuresLesson 7 of 825 min

List and dictionary comprehensions

Promised back in the for loops lesson: a shorter way to write the build-a-new-list pattern. Comprehensions are one of the things that make Python code look like Python, and they are also one of the easiest features to overuse.

The pattern they replace

You have written this shape repeatedly:

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

doubled = []
for number in numbers:
    doubled.append(number * 2)

print(doubled)
[2, 4, 6, 8, 10]

Four lines, three of which are bookkeeping. As a comprehension:

doubled = [number * 2 for number in numbers]

Same result. The structure is:

[ what to keep    for  each item  in  the source ]

Read it right to left at first: "for each number in numbers, keep number times two." After a week you will read it left to right without noticing.

Adding a condition

evens = []
for number in numbers:
    if number % 2 == 0:
        evens.append(number)

becomes:

evens = [number for number in numbers if number % 2 == 0]

The if goes at the end and filters: items failing it never reach the list.

Both together:

doubled_evens = [n * 2 for n in numbers if n % 2 == 0]
print(doubled_evens)
[4, 8]

Filter first, then transform what survives.

if/else is different, and goes elsewhere

This is the part people get wrong:

labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
print(labels)
['odd', 'even', 'odd', 'even', 'odd']

Note the position. An if at the end filters. An if/else at the front chooses a value — it is the conditional expression from module 3, not a filter.

The reason is simple: a filter can drop an item, so it belongs with the iteration. An if/else always produces something, so it belongs where the value is described.

Getting these confused gives a SyntaxError, which is at least a quick failure:

[n for n in numbers if n % 2 == 0 else 0]    # SyntaxError

If you want every item, with a substitute for the ones that fail, you want the front form:

[n if n % 2 == 0 else 0 for n in numbers]    # [0, 2, 0, 4, 0]

Working on strings

words = ["python", "is", "great"]

lengths = [len(w) for w in words]              # [6, 2, 5]
upper = [w.upper() for w in words]             # ['PYTHON', 'IS', 'GREAT']
long_words = [w for w in words if len(w) > 3]  # ['python', 'great']

A common cleaning job, stripping and dropping blanks in one line:

raw = ["  apple ", "", "  banana", "   "]
cleaned = [line.strip() for line in raw if line.strip()]
print(cleaned)
['apple', 'banana']

line.strip() runs twice, which is slightly wasteful and perfectly normal here. Readability wins for a list this size.

Dictionary comprehensions

Same idea, curly brackets and a colon:

words = ["python", "is", "great"]

lengths = {word: len(word) for word in words}
print(lengths)
{'python': 6, 'is': 2, 'great': 5}

Inverting a dictionary:

prices = {"tea": 40, "coffee": 120}
by_price = {value: key for key, value in prices.items()}
print(by_price)
{40: 'tea', 120: 'coffee'}

Careful — if two items share a price, one silently overwrites the other. Values are not guaranteed unique, so inverting is only safe when you know they are.

Filtering works the same way:

expensive = {k: v for k, v in prices.items() if v > 50}

Set comprehensions

Curly brackets, no colon:

text = "hello world"
unique_letters = {letter for letter in text if letter != " "}
print(unique_letters)

Unordered and deduplicated, as sets are.

Nested comprehensions

They work, and this is where to start being careful.

Flattening:

grid = [[1, 2], [3, 4], [5, 6]]
flat = [n for row in grid for n in row]
print(flat)
[1, 2, 3, 4, 5, 6]

The loops read in the same order you would write them normally — outer first, then inner. That surprises people who expect the reverse.

Building the grid from the list-methods lesson, where [[0] * 3] * 3 failed:

grid = [[0] * 3 for _ in range(3)]
grid[0][0] = 1
print(grid)
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]

Each pass creates a new inner list, which is exactly what multiplication did not do. This is the correct way to build a grid, and worth memorising.

When not to use one

Comprehensions are a tool for one job: building a collection from another collection. Push them further and they stop being readable.

Do not use one for side effects:

[print(n) for n in numbers]      # works, and is wrong

That builds a list of None values and throws it away. If you are not keeping the result, write a normal loop.

Do not nest deeply:

result = [f(x, y) for x in xs if p(x) for y in ys if q(x, y)]

Technically valid. Nobody can read it, including you next month. Two loops and an if are fine.

Do not force complex logic in. If the body needs several steps, or a try, or a comment to explain it, a normal loop is the better answer. Comprehensions should make code shorter and clearer. When they only make it shorter, they are costing you.

A reasonable rule: if it does not fit comfortably on one line, write the loop.

A word on generators

Swap the brackets for round ones and you get a generator:

squares = (n ** 2 for n in range(1000000))

That does not build a million items. It produces them one at a time as they are asked for, which matters when the collection is large or you only need to walk it once:

total = sum(n ** 2 for n in range(1000000))

No million-item list is ever created. When you are passing straight into sum(), max(), any() or all(), drop the square brackets and save the memory.

Practice

  1. Build a list of the squares of 1 to 10, first with a loop, then with a comprehension.

  2. From range(1, 51), make a list of numbers divisible by 3 but not 5.

  3. Given names = ["priya", "ARJUN", "Sneha"], produce them all properly capitalised.

  4. Given ["3", "x", "7", "", "12"], build a list of integers, skipping anything that is not a number.

  5. Label each number in range(1, 11) as "even" or "odd" using an if/else comprehension. Then explain why the if sits at the front.

  6. Build {word: len(word)} for a sentence's words, then filter it to words longer than four letters.

  7. Flatten [[1, 2], [3, 4], [5, 6]]. Then build a 4 x 4 grid of zeros correctly and prove that changing one cell does not change others.

  8. Take this and rewrite it as a comprehension, then decide which version you would rather read:

    result = []
    for student in students:
        if student["marks"]:
            result.append(sum(student["marks"]) / len(student["marks"]))
    

Next: choosing between all four structures, with worked examples.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship