List Comprehensions vs Generator Expressions in Python: When to Use Each

·

·

List Comprehensions vs Generator Expressions

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 bytes

The 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

FeatureList ComprehensionGenerator Expression
Syntax[expr for item in iterable](expr for item in iterable)
Memory usageStores all elements in memoryGenerates items one by one
Speed (small data)Faster (precomputed)Slight overhead per item
IterationCan reuse multiple timesCan be consumed only once
IndexingSupports indexingNo 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.