List Comprehensions in Python | Python Course | Dataplexa

List Comprehensions in Python

One of the most beloved features of Python is the ability to build lists in a single, readable line using list comprehensions. Instead of writing a full for loop, an empty list, and repeated append() calls, you express the entire operation in one compact expression. Python programmers use list comprehensions constantly — they are faster to write, faster to read, and in many cases faster to run than the equivalent loop.

This lesson starts with the basics and builds to filtering, nested comprehensions, the walrus operator, performance considerations, and when a regular loop is the better choice.

The Problem List Comprehensions Solve

Consider doubling every number in a list. The traditional loop takes four lines to express one simple idea:

# Traditional loop — four lines for one idea
nums   = [1, 2, 3, 4, 5]
doubled = []
for n in nums:
    doubled.append(n * 2)
print(doubled)

# List comprehension — one line, same result
doubled = [n * 2 for n in nums]
print(doubled)
[2, 4, 6, 8, 10] [2, 4, 6, 8, 10]

Both produce identical output. The comprehension reads almost like English: "give me n * 2 for each n in nums." That directness is the point.

Basic Syntax and Common Transformations

The structure is always: [expression for item in iterable]. The expression is evaluated once for every item — the result is always a new list.

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

# Uppercase every word
upper  = [w.upper() for w in words]
print(upper)

# Get the length of each word
lengths = [len(w) for w in words]
print(lengths)

# Capitalise and add exclamation
shout  = [w.capitalize() + "!" for w in words]
print(shout)

# Extract first character of each word
initials = [w[0] for w in words]
print(initials)

# Build a list of (word, length) tuples
pairs  = [(w, len(w)) for w in words]
print(pairs)
['HELLO', 'WORLD', 'PYTHON', 'IS', 'GREAT'] [5, 5, 6, 2, 5] ['Hello!', 'World!', 'Python!', 'Is!', 'Great!'] ['h', 'w', 'p', 'i', 'g'] [('hello', 5), ('world', 5), ('python', 6), ('is', 2), ('great', 5)]
  • Any expression works after the opening bracket — string methods, arithmetic, function calls, even tuple literals.
  • The original iterable is never modified — comprehensions always return a new list.
  • Any iterable works: lists, strings, tuples, ranges, dictionaries, sets, files.

Using range() in a List Comprehension

range() pairs naturally with comprehensions to generate numeric sequences without a pre-existing list.

# Squares of 1 through 10
squares  = [x ** 2 for x in range(1, 11)]
print("Squares:", squares)

# Cubes of 1 through 5
cubes    = [x ** 3 for x in range(1, 6)]
print("Cubes  :", cubes)

# All even numbers from 0 to 20
evens    = [x for x in range(0, 22, 2)]
print("Evens  :", evens)

# Fibonacci-style: build list from range and formula
# Celsius to Fahrenheit for whole-degree values 0-40
temps_f  = [round((c * 9/5) + 32, 1) for c in range(0, 45, 5)]
print("Temps °F:", temps_f)
Squares: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] Cubes : [1, 8, 27, 64, 125] Evens : [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20] Temps °F: [32.0, 41.0, 50.0, 59.0, 68.0, 77.0, 86.0, 95.0, 104.0]

Filtering with a Condition

Add an if clause at the end to include only items that pass a test. Items where the condition is False are silently discarded.

nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

# Even numbers only
evens = [n for n in nums if n % 2 == 0]
print("Even:", evens)

# Odd numbers only
odds  = [n for n in nums if n % 2 != 0]
print("Odd :", odds)

# Numbers divisible by 3
by3   = [n for n in nums if n % 3 == 0]
print("÷3  :", by3)

# Filter real data — keep in-stock products
products = [
    {"name": "Laptop",   "price": 1200, "stock": 5},
    {"name": "Monitor",  "price": 450,  "stock": 0},
    {"name": "Keyboard", "price": 80,   "stock": 20},
    {"name": "Webcam",   "price": 120,  "stock": 0},
    {"name": "Mouse",    "price": 35,   "stock": 50},
]
in_stock = [p["name"] for p in products if p["stock"] > 0]
print("In stock:", in_stock)

affordable = [p["name"] for p in products if p["price"] < 200 and p["stock"] > 0]
print("Affordable and in stock:", affordable)
Even: [2, 4, 6, 8, 10, 12] Odd : [1, 3, 5, 7, 9, 11] ÷3 : [3, 6, 9, 12] In stock: ['Laptop', 'Keyboard', 'Mouse'] Affordable and in stock: ['Keyboard', 'Mouse']
  • Structure with filter: [expression for item in iterable if condition]
  • You can combine conditions with and / or in one if clause.
  • The if tests the original item — the expression before for transforms it.

Transform and Filter Together

The real power comes from combining transformation and filtering in the same comprehension — include only certain items and change them at the same time.

nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Square only the even numbers
even_squares = [n ** 2 for n in nums if n % 2 == 0]
print("Even squares:", even_squares)

# Clean messy strings — strip whitespace, keep non-empty
raw = ["  apple ", "", "  ", "banana", "  cherry  ", "   "]
clean = [s.strip() for s in raw if s.strip()]
print("Cleaned:", clean)

# Extract discounted prices for expensive items only
prices = [15, 450, 8500, 80, 1200, 35]
big_discounted = [round(p * 0.85, 2) for p in prices if p >= 100]
print("15% off (items ≥100):", big_discounted)

# Extract emails for active users
users = [
    {"email": "a@x.com", "active": True},
    {"email": "b@x.com", "active": False},
    {"email": "c@x.com", "active": True},
]
active_emails = [u["email"].upper() for u in users if u["active"]]
print("Active emails:", active_emails)
Even squares: [4, 16, 36, 64, 100] Cleaned: ['apple', 'banana', 'cherry'] 15% off (items ≥100): [382.5, 7225.0, 1020.0] Active emails: ['A@X.COM', 'C@X.COM']
  • Python evaluates the condition first — only items that pass are then transformed.
  • This single line replaces: empty list, loop, if-check, transformation, and append — five lines of code.

Inline if/else — The Ternary Expression

Use an inline if/else (ternary) directly in the expression part to choose between two output values for every item. Every item is included — the ternary just decides which value it gets. Note the position: the ternary goes before for, not after it.

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

# Label each number as even or odd — all items included
labels = ["even" if n % 2 == 0 else "odd" for n in nums]
print(labels)

# Replace negatives with 0, keep positives as-is
data    = [-3, 7, -1, 4, 0, -9, 2]
clipped = [n if n > 0 else 0 for n in data]
print(clipped)

# Apply a 20% discount to expensive items, 10% to cheap ones
prices     = [50, 1200, 30, 8500, 75, 450]
discounted = [round(p * 0.8, 2) if p >= 100 else round(p * 0.9, 2) for p in prices]
print("Discounted:", discounted)

# Grade assignment
scores = [95, 72, 88, 45, 61, 80]
grades = ["A" if s >= 90 else "B" if s >= 75 else "C" if s >= 60 else "F" for s in scores]
print("Grades:", grades)
['odd', 'even', 'odd', 'even', 'odd', 'even'] [0, 7, 0, 4, 0, 0, 2] Discounted: [45.0, 960.0, 27.0, 6800.0, 67.5, 360.0] Grades: ['A', 'C', 'B', 'F', 'C', 'B']
  • Structure: [val_if_true if condition else val_if_false for item in iterable]
  • This is different from a trailing if — the trailing if removes items, the ternary chooses between two values while keeping all items.
  • Nested ternaries work but become hard to read quickly — limit to two conditions maximum.

Nested List Comprehensions

You can nest one comprehension inside another to work with two-dimensional data — grids, matrices, or lists of lists. The loop order in a nested comprehension matches the equivalent nested loops: outer loop first, inner loop second.

# Flatten a 2D matrix into a 1D list
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat   = [val for row in matrix for val in row]
print("Flat:", flat)

# Filter while flattening — keep only even values
even_flat = [val for row in matrix for val in row if val % 2 == 0]
print("Even values:", even_flat)

# Build a 3x3 multiplication table
table = [[r * c for c in range(1, 4)] for r in range(1, 4)]
for row in table:
    print(row)

# Transpose a matrix (swap rows and columns)
original   = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
transposed = [[row[i] for row in original] for i in range(3)]
print("Transposed:")
for row in transposed:
    print(row)
Flat: [1, 2, 3, 4, 5, 6, 7, 8, 9] Even values: [2, 4, 6, 8] [1, 2, 3] [2, 4, 6] [3, 6, 9] Transposed: [1, 4, 7] [2, 5, 8] [3, 6, 9]
  • Flatten: [expr for outer in list for inner in outer] — reads left to right like the equivalent nested loops.
  • Build 2D: [[expr for col in range] for row in range] — result is a list of lists.
  • Transpose is a classic data-science operation — reorganises a table so columns become rows.
  • Beyond two levels of nesting, a regular loop with clear variable names is almost always clearer.

Comprehensions with String Operations

Strings are iterable in Python — you can loop over characters directly, which opens up powerful one-liner text processing.

# Extract all vowels from a string
sentence = "Python is an incredible language"
vowels   = [c for c in sentence if c.lower() in "aeiou"]
print("Vowels:", vowels)
print("Count :", len(vowels))

# Count characters that are digits
mixed = "p3th0n 1s aw3some"
digits = [c for c in mixed if c.isdigit()]
print("Digits:", digits)

# Reverse each word in a sentence
words    = "list comprehensions are powerful".split()
reversed_words = [w[::-1] for w in words]
print("Reversed words:", reversed_words)

# Clean a list of tags — lowercase, strip, remove empty
raw_tags = ["  Python  ", "DATA SCIENCE", "", " ai ", "Python", "  "]
clean_tags = list({t.lower().strip() for t in raw_tags if t.strip()})
print("Clean unique tags:", sorted(clean_tags))
Vowels: ['o', 'i', 'a', 'i', 'e', 'i', 'e', 'a', 'u', 'a', 'e'] Count : 11 Digits: ['3', '0', '1', '3'] Reversed words: ['tsil', 'snoisneherpmoc', 'era', 'lufrewop'] Clean unique tags: ['ai', 'data science', 'python']
  • w[::-1] reverses a string using slice notation — a Pythonic trick worth knowing.
  • The last example uses a set comprehension {...} inside list() to deduplicate automatically.

Comprehension vs map() and filter()

Before list comprehensions, Python programmers used map() and filter() with lambdas. Comprehensions are now the preferred Pythonic style — they are more readable and avoid the extra list() wrapping.

nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# map() vs comprehension for transformation
doubled_map  = list(map(lambda n: n * 2, nums))
doubled_comp = [n * 2 for n in nums]
print(doubled_map  == doubled_comp)    # True — identical results

# filter() vs comprehension for filtering
evens_filter = list(filter(lambda n: n % 2 == 0, nums))
evens_comp   = [n for n in nums if n % 2 == 0]
print(evens_filter == evens_comp)      # True — identical results

# map() can still be useful with named functions (no lambda needed)
import math
roots_map  = list(map(math.sqrt, nums))
roots_comp = [math.sqrt(n) for n in nums]
print(roots_map == roots_comp)         # True
True True True
  • Comprehensions are more readable than map/filter + lambda in most cases — PEP 8 recommends them.
  • map(named_function, list) is still clean and valid when you already have a named function — no lambda needed.
  • map() and filter() return lazy iterators — they do not build the list until you wrap with list().

Performance — Comprehension vs Loop

List comprehensions are generally faster than equivalent loops in Python because they are implemented in optimised C internally. The difference grows with list size.

import time

N = 1_000_000

# Loop timing
start = time.time()
result = []
for i in range(N):
    result.append(i * 2)
loop_time = time.time() - start

# Comprehension timing
start = time.time()
result = [i * 2 for i in range(N)]
comp_time = time.time() - start

print(f"Loop          : {loop_time:.4f}s")
print(f"Comprehension : {comp_time:.4f}s")
print(f"Speedup       : {loop_time / comp_time:.1f}x faster")
Loop : 0.1423s Comprehension : 0.0891s Speedup : 1.6x faster
  • Comprehensions avoid the overhead of repeated list.append() attribute lookups on each iteration.
  • The speedup is typically 1.5–2x for simple transformations — meaningful at scale.
  • For very large data (millions of items), use a generator expression (x for x in ...) instead — it does not build the full list in memory.

When Not to Use a List Comprehension

# BAD — comprehension used only for side effects (result thrown away)
items = ["apple", "banana", "cherry"]
# [print(item) for item in items]   # don't do this

# GOOD — use a loop when the purpose is side effects
for item in items:
    print(item)

# BAD — over-nested, unreadable
# result = [x for sublist in [lst for lst in matrix if any(x > 5 for x in lst)] for x in sublist if x % 2 == 0]

# GOOD — break complex logic into readable steps
big_rows   = [lst for lst in matrix if any(x > 5 for x in lst)]
even_vals  = [x for row in big_rows for x in row if x % 2 == 0]
apple banana cherry
  • Side effects — if the loop body exists to call a function for its effect (print, write to file), use a regular loop. A comprehension that discards its result is misleading.
  • Complex logic — if the expression or condition needs multiple lines, a loop with named variables is clearer and easier to debug.
  • Large data — a comprehension builds the entire list in memory. Use a generator expression for huge datasets: (x * 2 for x in big_range).
  • Debugging — you cannot add a breakpoint inside a comprehension. During debugging, a loop lets you inspect each step.

Quick Reference Table

PatternSyntaxWhat It Does
Basic transform[expr for x in iterable]Apply expression to every item
Filter[expr for x in iterable if cond]Include only matching items
Transform + filter[f(x) for x in it if cond]Transform only passing items
Ternary[a if cond else b for x in it]Choose between two values per item
Flatten 2D[x for row in matrix for x in row]Collapse list of lists to flat list
Build 2D[[expr for c in r] for r in r]Build matrix / list of lists
From range[expr for x in range(n)]Generate numeric sequence
String chars[c for c in string if cond]Filter or extract characters
Generator(expr for x in iterable)Lazy version — saves memory

Practice

Write a list comprehension that produces squares of numbers 1 through 5.



Where does the if filter condition go in a list comprehension?



What type of object does a list comprehension always produce?



What is the key difference between a trailing if filter and an inline if/else ternary?



Name one situation where a regular for loop is preferred over a list comprehension.



What should you use instead of a list comprehension when processing very large datasets to save memory?



Quick Quiz

What does [x * 2 for x in range(1, 4)] produce?






Which correctly filters a list to keep only values greater than 3?





What does ["yes" if x % 2 == 0 else "no" for x in range(1, 4)] produce?





What does [val for row in [[1,2],[3,4]] for val in row] produce?





Why is a generator expression preferred over a list comprehension for very large datasets?





What does [n**2 for n in range(1,11) if n % 2 == 0] produce?





NEXT UP
Dictionary Comprehensions in Python
Apply the same comprehension power to dictionaries — build, transform, and filter key-value pairs in a single clean expression.