All 57 lessons
Lesson 16 of 57 · Data Structures
Python Comprehensions: Transform and Filter in One Line
By Skillancy Editorial Team
Run the code in the free Python Compiler or on your own computer. Lessons that read files use the FreshBasket practice data: download the dataset (ZIP).
You already know how to build a list with a loop: create an empty list, loop, append. It works, but three lines are spent on bookkeeping for every one line of real logic. A comprehension compresses that pattern into a single expression that says what you want: "the price of every product that is in stock". Experienced Python programmers use comprehensions constantly, and you will see them in almost every real script.
The good news is that a comprehension is only a loop in a different order. If you can write the loop, you can write the comprehension. This lesson shows the pattern step by step, covers lists, dictionaries and sets, and, just as importantly, tells you when a plain loop is the better choice.
The pattern
# loop version
result = []
for x in items:
if condition(x):
result.append(expression(x))
# comprehension version
result = [expression(x) for x in items if condition(x)]Read a comprehension from the middle: for x in items is the loop, if condition(x) is the filter, and the part at the front is what gets stored for each surviving item.
Worked examples
Example 1: Transform every item
Question: Convert a list of prices in rupees to paise.
prices = [68, 45, 96, 62, 120]
paise = [p * 100 for p in prices]
print(paise)
names = ["milk", "curd", "eggs"]
print([n.title() for n in names])
print([len(n) for n in names])Output
[6800, 4500, 9600, 6200, 12000]
['Milk', 'Curd', 'Eggs']
[4, 4, 4]How it works: For each p in prices, the expression p * 100 is evaluated and the result is added to the new list. The original list is untouched. The same shape works for any transformation: calling a method, taking a length, formatting a string.
Reading the result: The first price 68 became 6800 paise, and the list kept its length of 5. A transforming comprehension always produces as many items as it was given.
Example 2: Filter with a condition
Question: Keep only the prices above 60.
prices = [68, 45, 96, 62, 120, 30]
expensive = [p for p in prices if p > 60]
print(expensive)
discounted = [p * 0.9 for p in prices if p > 60]
print(discounted)Output
[68, 96, 62, 120]
[61.2, 86.4, 55.800000000000004, 108.0]How it works: The if at the end is a filter: items that fail it are skipped entirely. The front expression can be just the item itself, or a calculation on it, as in the second line, which filters first and then transforms.
Reading the result: 4 of 6 prices passed the filter, giving [68, 96, 62, 120]. When there is a filter, the output is usually shorter than the input.
Example 3: Choose a value with if-else
Question: Label each delivery as "fast" or "slow".
minutes = [12, 35, 18, 41, 9, 27]
labels = ["fast" if m <= 20 else "slow" for m in minutes]
print(labels)
print(labels.count("fast"))Output
['fast', 'slow', 'fast', 'slow', 'fast', 'slow']
3How it works: When the if comes before the for, it must have an else, because it picks a value for every item. When the if comes after the loop, it filters. This is the most common point of confusion with comprehensions, so remember: front if-else chooses, back if filters.
Reading the result: All 6 deliveries got a label, and 3 of them were fast. Compare that with a filter, which would have dropped the slow ones.
Example 4: Comprehensions with real data
Question: From the real orders file, list the delivery times of the first 15 delivered orders.
import pandas as pd
orders = pd.read_csv("orders.csv")
delivered = orders[orders["order_status"] == "Delivered"]
times = [int(m) for m in delivered["delivery_minutes"].head(15)]
print(times)
late = [m for m in delivered["delivery_minutes"] if m > 45]
print(len(late), len(delivered))Output
[34, 24, 37, 29, 20, 30, 25, 23, 18, 20, 36, 33, 27, 19, 35]
7 5391How it works: A pandas column can be looped over like a list. The first comprehension converts each value to a whole number, and the second counts the deliveries slower than 45 minutes. In pandas itself you would write (delivered["delivery_minutes"] > 45).sum(), which is faster for big data; comprehensions are for plain Python collections and small data.
Reading the result: 7 of 5391 delivered orders took more than 45 minutes, a share of 0.1%.
Example 5: Dictionary comprehensions
Question: Build a dictionary of product name to price, and a version with 10% off.
products = pd.read_csv("products.csv")
print(list(products.columns))
rows = products.head(5).to_dict("records")
price_of = {r["product_name"]: r["unit_price"] for r in rows}
print(price_of)
sale = {name: round(price * 0.9, 2) for name, price in price_of.items()}
print(sale)Output
['product_id', 'product_name', 'category', 'brand', 'unit_price', 'cost_price', 'shelf_life_days']
{'Banana Robusta': 48, 'Tomato Hybrid': 36, 'Onion': 42, 'Potato': 38, 'Apple Shimla': 180}
{'Banana Robusta': 43.2, 'Tomato Hybrid': 32.4, 'Onion': 37.8, 'Potato': 34.2, 'Apple Shimla': 162.0}How it works: A dictionary comprehension has a key: value pair at the front. to_dict("records") turns the DataFrame into a list of dictionaries, one per row, which is the format we met in the dictionaries lesson. The second comprehension loops over items() and rebuilds the dictionary with new values.
Reading the result: The first dictionary maps 5 product names to their prices, and the sale dictionary has the same keys with prices reduced by ten percent.
Example 6: Set comprehensions
Question: Find the distinct first letters of product names.
letters = {r["product_name"][0] for r in products.to_dict("records")}
print(sorted(letters))
emails = ["A@x.com", "a@x.com", "b@x.com"]
print(len({e.lower() for e in emails}))Output
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'M', 'N', 'O', 'P', 'S', 'T']
2How it works: Curly braces with a single expression (no colon) build a set, so duplicates vanish. The second line counts distinct emails after lowercasing, a common cleaning step.
Reading the result: Product names start with 16 different letters, and the three emails reduce to 2 distinct ones once capitalisation is ignored.
Example 7: Two loops and flattening
Question: Flatten a list of lists, and generate every store-slot combination.
baskets = [["Milk", "Eggs"], ["Bread"], ["Milk", "Curd", "Eggs"]]
flat = [item for basket in baskets for item in basket]
print(flat)
stores = ["S01", "S02"]
slots = ["morning", "evening"]
combos = [(s, t) for s in stores for t in slots]
print(combos)Output
['Milk', 'Eggs', 'Bread', 'Milk', 'Curd', 'Eggs']
[('S01', 'morning'), ('S01', 'evening'), ('S02', 'morning'), ('S02', 'evening')]How it works: Multiple for clauses are read in the same order as nested loops: the first for is the outer loop and the second is the inner loop. So [item for basket in baskets for item in basket] is the same as looping over baskets and then over each basket's items.
Reading the result: The three baskets flatten into 6 items, and two stores crossed with two slots give 4 combinations. Flattening is common when you work with API responses.
Example 8: Comprehension with zip and enumerate
Question: Compute the line total for each order item, and number the results.
qty = [2, 1, 3]
unit_price = [68, 120, 45]
totals = [q * p for q, p in zip(qty, unit_price)]
print(totals)
numbered = [f"{i}. {t}" for i, t in enumerate(totals, start=1)]
print(numbered)Output
[136, 120, 135]
['1. 136', '2. 120', '3. 135']How it works: zip walks several lists in step, and enumerate adds a counter. Both give tuples that unpack directly in the for part of the comprehension.
Reading the result: The basket's line totals are [136, 120, 135], so the grand total is ₹391.
Example 9: Generator expressions
Question: Sum the squares of a large range without building a list in memory.
total = sum(n * n for n in range(1, 1_000_001))
print(f"{total:,}")
has_big = any(p > 100 for p in prices)
all_positive = all(p > 0 for p in prices)
print(has_big, all_positive)Output
333,333,833,333,500,000
True TrueHow it works: Replace the square brackets with parentheses (or none at all, inside a function call) and you get a generator expression: it produces one value at a time instead of building the full list. sum, any, all, min and max accept generators directly, which saves memory on large inputs.
Reading the result: The sum of squares was 333,333,833,333,500,000. Use a generator when you only need to consume the values once.
Example 10: When a loop is clearer
Question: Why not put everything in a comprehension?
def category(m):
if m <= 15:
return "express"
elif m <= 30:
return "standard"
return "slow"
minutes = [12, 35, 18, 41, 9, 27]
print([category(m) for m in minutes])
# The nested ternary below is legal but hard to read:
print(["express" if m <= 15 else "standard" if m <= 30 else "slow" for m in minutes])Output
['express', 'slow', 'standard', 'slow', 'express', 'standard']
['express', 'slow', 'standard', 'slow', 'express', 'standard']How it works: Both lines give the same answer. The first moves the logic into a small, named function and keeps the comprehension short. The second packs two decisions into one expression, which works but is hard to scan. A good rule is: if a comprehension does not fit comfortably on one or two lines, or needs more than one if/else, use a function or a normal loop.
Reading the result: The first list classified 6 deliveries, with 2 express. Clear code beats clever code.
Common mistakes
1. Putting the filter before the loop without an else.
labels = ["fast" for m in [12, 35] if m <= 20 else "slow"]Output
File "example.py", line 1
labels = ["fast" for m in [12, 35] if m <= 20 else "slow"]
^^^^
SyntaxError: invalid syntax2. Using a comprehension for side effects. [print(x) for x in items] builds a useless list; use a normal loop.
3. Over-nesting. More than two for clauses is a sign to use a loop.
4. Forgetting that the loop variable leaks in old code. In Python 3, the variable in a comprehension stays inside it, unlike a normal for loop.
5. Building huge lists you only loop over once. Use a generator expression instead.
Practice questions
Practice 1 (Easy): Square the evens
Using a comprehension, make a list of the squares of the even numbers from 1 to 10.
Show solution
squares = [n * n for n in range(1, 11) if n % 2 == 0]
print(squares)Output
[4, 16, 36, 64, 100]The filter keeps even numbers and the front expression squares them. The result has 5 values.
Practice 2 (Medium): Clean and filter city names
From [" pune", "DELHI ", "", "Mumbai", " "], produce a list of title-cased city names, ignoring empty or blank entries.
Show solution
raw = [" pune", "DELHI ", "", "Mumbai", " "]
clean = [c.strip().title() for c in raw if c.strip()]
print(clean)Output
['Pune', 'Delhi', 'Mumbai']An empty string is false in a condition, so if c.strip() keeps only entries that still contain text after stripping. 3 names survive.
Practice 3 (Hard): Orders per city dictionary
Using stores.csv and orders.csv, build a dictionary mapping each store id to its number of orders, using a dictionary comprehension, and print the busiest store.
Show solution
stores = pd.read_csv("stores.csv")
order_ids = orders["store_id"].tolist()
counts = {sid: order_ids.count(sid) for sid in stores["store_id"]}
busiest = max(counts, key=counts.get)
print(busiest, counts[busiest])Output
S07 689list.count is simple but slow for large data; in pandas you would use value_counts(). For our 6,000 orders it is fine, and the busiest store is S07 with 689 orders.
Key takeaways
- A comprehension is
[expression for item in iterable if condition], a loop written inside out. - A trailing
iffilters; a leadingx if cond else ychooses a value. - Curly braces with
key: valuebuild dictionaries, and with a single expression build sets. - Multiple
forclauses read like nested loops, outer first. - Use generator expressions (parentheses) for one-time consumption such as
sum. - If it gets hard to read, use a function or a loop instead.
Frequently asked questions
Are comprehensions faster than loops?
Usually a little, because the looping happens inside the interpreter's optimised code. The bigger benefit is readability, not speed.
Can I use else in the filter position?
No. A trailing if can only filter. To choose between two values, put value_a if condition else value_b at the front.
What is the difference between [...] and (...)?
Square brackets build the whole list immediately. Parentheses build a generator that produces values lazily, one at a time.
Is there a tuple comprehension?
No. Parentheses make a generator. To get a tuple, write tuple(x for x in items).
Can I call functions inside a comprehension?
Yes, any expression works, including your own functions. This is the best way to keep a complex comprehension readable.
Should I use comprehensions in pandas code?
Use them for small plain-Python tasks, like building column lists. For whole columns, use vectorised pandas operations, which are much faster.
Keep learning Python
One email a week with Python and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.
Ready to practise? Try the Python interview questions