When I first started learning Python, I thought list comprehensions were the coolest thing ever. Python developers love shortcuts that make code clean and expressive. Two such shortcuts are list comprehensions and generator expressions. They look almost the same, one uses square brackets, the other use parentheses, but the difference can impact memory, performance, and how your program behaves.
My First List Comprehensions
List comprehensions are probably one of the first elegant things you’ll fall in love with. I remember the first time I replaced a bulky loop with one line and felt like a pro:
# Create a list of numbers from 1 to 5
n = 5
numbers = [x for x in range(1, n+1)]
print(numbers)
# Create a new list with the squares
squares = [x ** 2 for x in numbers]
print(squares)
Output:
[1, 2, 3, 4, 5]
[1, 4, 9, 16, 25]No more for loops and append(). Clean, readable, and powerful. I even started overusing them.
Another common usage is Filtering Data
fruits = ['apple', 'banana', 'cherry', 'date', 'fig']
long_fruits = [fruit for fruit in fruits if len(fruit) > 4]
print(long_fruits)
Output:
['apple', 'banana', 'cherry']Another time, I was working on a small script to pick out odd numbers. Instead of a loop, I wrote:
range_start = 1
range_end = 10
odd_numbers = [num for num in range(range_start, range_end + 1) if num % 2 != 0]
print(odd_numbers)
Output:
[1, 3, 5, 7, 9]Discovering Generator Expressions
If you try building a list of 100 million numbers using a comprehension. Your code will run slow and it ate up so much memory, your laptop will almost froze. That’s why you have to discover generator expressions. They look almost the same, but behave differently:
generator_expression = (x for x in range(10))
print(type(generator_expression))
print(generator_expression)Output
<class 'generator'>
<generator object <genexpr> at 0x000001D08A429FF0>Instead of a full list, you will get a “generator object” – It only give you values when you ask for them.
numbers = range(1, 10)
even_numbers = (num for num in numbers if num % 2 == 0)
print(type(even_numbers ))
print(even_numbers )
for i in even_numbers:
print(i, end=" ")Output:
<class 'generator'>
<generator object <genexpr> at 0x000001A8C5124D40>
2 4 6 8 One of the most important pitfalls about generators is that a generator expression can only be consumed once.
gen = (x for x in range(5))
# First consumption
print("First run:", list(gen))
# Try to consume again
print("Second run:", list(gen))
Output
First run: [0, 1, 2, 3, 4]
Second run: []On the first run, Python iterates through the generator, pulling values one by one until it’s exhausted.
By the second run, there are no values left — the generator has already done its job.
Performance and Memory Efficiency
The real strength of generators is in handling huge datasets. Let’s compare:
from sys import getsizeof
import time
start_time = time.time()
list_com = [i for i in range(100000000) if i % 2 == 0]
print(f"List Comprehension: {time.time() - start_time} seconds, {getsizeof(list_com)} bytes")
start_time = time.time()
gen_exp = (i for i in range(100000000) if i % 2 == 0)
print(f"Genertator Expression: {time.time() - start_time} seconds, {getsizeof(gen_exp)} bytes")Output (will vary by machine)
List Comprehension: 6.021908283233643 seconds, 411943896 bytes
Genertator Expression: 0.0 seconds, 208 bytesThe generator is almost free in terms of memory, while the list eats up ~40 MB. That’s because the list creates and stores all numbers at once, while the generator only knows how to produce the next number.
Both list comprehensions and generator expressions are Python superpowers. The difference boils down to memory vs convenience:
- Lists give you everything upfront, at the cost of memory.
- Generators give you values on demand, at the cost of being one-time use.
Key Difference in a table
| Feature | List Comprehension | Generator Expression |
|---|---|---|
| Syntax | [expr for item in iterable] | (expr for item in iterable) |
| Memory usage | Stores all elements in memory | Generates items one by one |
| Speed (small data) | Faster (precomputed) | Slight overhead per item |
| Iteration | Can reuse multiple times | Can be consumed only once |
| Indexing | Supports indexing | No direct indexing |
Practical Guidelines
- Use list comprehensions when you need the full result and the dataset is reasonably small.
- Use generator expressions for very large datasets, pipelines, or when you only need to loop once.
- Default to generator expressions for large or streaming data. Switch to list comprehensions only when you actually need random access or multiple passes.
