One of the most beautiful things about Python is the expressive, readable syntax it offers — especially in the form of list comprehensions and generator expressions.
But behind the elegance lies some subtle performance and behavior differences that often come up in interviews and production code. This post explains:
- What list comprehensions really are
- What generator expressions are
- What
next()
has to do with it - When to use
[]
vs()
- How to explain it all in an interview
🔷 What Is a List Comprehension?
A list comprehension is a concise way to create lists in Python. It’s like a one-liner for
loop that builds a list.
squares = [x * x for x in range(5)]
print(squares) # ➜ [0, 1, 4, 9, 16]
This is equivalent to:
squares = []
for x in range(5):
squares.append(x * x)
✅ It evaluates all elements immediately and stores them in memory as a list
.
🔷 What Is a Generator Expression?
A generator expression looks similar — but uses ()
instead of []
.
squares = (x * x for x in range(5))
print(squares) # ➜ <generator object>
print(next(squares)) # ➜ 0
✅ It returns a generator object that computes one value on demand.
🔍 [ ]
vs ( )
— The Core Difference
Syntax | Result Type | Behavior |
---|---|---|
[x for x in iterable] | list | Eager evaluation — builds full list in memory |
(x for x in iterable) | generator | Lazy evaluation — yields values one by one |
🔁 Why Does next()
Work on Generators?
When you use a generator expression, Python doesn’t compute the values right away.
gen = (x * x for x in range(3))
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 4
print(next(gen)) # ❌ StopIteration
- The generator remembers its state
next()
gives you the next value each time- When there are no more, it raises
StopIteration
✅ Looping Over a Generator
You usually don’t call next()
manually. Instead:
for val in (x * x for x in range(3)):
print(val)
This internally calls next()
for each value until exhausted.
🧠 List Comprehension vs Generator Expression — Deep Comparison
Feature | List Comprehension | Generator Expression |
---|---|---|
Syntax | [x for x in iterable] | (x for x in iterable) |
Output Type | list | generator |
Evaluation | Eager (builds full list) | Lazy (yields one value at a time) |
Memory Usage | ❌ Higher | ✅ Lower |
Speed (small data) | ✅ Slightly faster | Slightly slower |
Speed (large data) | ❌ Slower (due to memory) | ✅ Better for big data |
Best Used When | You need all data now | You’ll process one item at a time |
📁 Real-Life Example: Reading a Huge File
❌ BAD (List Comprehension)
lines = [line for line in open('bigfile.txt')]
Loads the entire file into memory — can crash your system.
✅ GOOD (Generator Expression)
lines = (line for line in open('bigfile.txt'))
Reads one line at a time — no memory overload.
🔧 Under the Hood: Iterators and next()
Generators follow the iterator protocol:
gen = (x for x in range(2))
print(iter(gen) is gen) # ✅ True — already an iterator
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # ❌ StopIteration
✅ You don’t need to call iter()
on a generator — it’s already iterable.
💡 Interview Tips
❓ Q: Why would you use a generator instead of a list?
A: When the dataset is too large, or you only need to read/process each item once, generator expressions save memory and improve performance.
❓ Q: Can a generator be reused?
A: No. Once a generator is exhausted, you must create a new one.
gen = (x for x in range(3))
list(gen) # ➜ [0, 1, 2]
list(gen) # ➜ [] (already consumed)
✅ Summary: [ ]
vs ( )
Expression | Result | When to Use |
---|---|---|
[x for x in ...] | Full list | You want to store or reuse values |
(x for x in ...) | Generator | You want memory-efficient streaming |
🧠 One Line to Remember
Use
[]
when you want all values now.
Use()
when you want one value at a time.
Leave a Reply