What is a generator in Python, and how is yield different from return?
A generator is a function that uses yield to produce values one at a time, pausing after each one and resuming where it left off on the next request. return ends the function and gives back one result, while yield hands out a value and keeps the function's state. Generators save memory because they never build the full list.
Explain it simply
Compare a full thali with a South Indian meal served on a banana leaf. With the thali, everything is cooked and placed on your plate at once, so you need a big plate. On the banana leaf, the server brings one item at a time, only when you are ready for it. The kitchen remembers what it has already served and continues from there.
A normal function that builds a list is the thali: it creates every value up front and returns them all. A generator is the banana leaf server: it gives one value, pauses, and continues only when you ask for the next.
Example
def squares_list(n):
result = []
for i in range(n):
result.append(i * i)
return result # builds the whole list, then returns once
def squares_gen(n):
for i in range(n):
yield i * i # hands out one value, then pauses here
gen = squares_gen(3)
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 4
# next(gen) now raises StopIteration: the generator is finished
for sq in squares_gen(4):
print(sq) # 0, 1, 4, 9 on separate lines
total = sum(x * x for x in range(10_000_000)) # generator expression, no huge list
print(total)How it works
- Calling a function that contains
yielddoes not run its body. It returns a generator object. - Each
next()runs the body until the nextyield, returns that value, and freezes the function: local variables and the current line are saved. - The next
next()resumes right after thatyield. - When the function ends (or hits
return), Python raisesStopIteration. Aforloop catches this for you and stops quietly. - A generator can only be used once. After it is exhausted, looping over it again gives nothing.
Generator expressions use round brackets: (x * x for x in data). They are the lazy version of a list comprehension [x * x for x in data].
Real-world use
- Reading large files: a 5 GB log file read line by line with
for line in open(path)(the file object itself is an iterator). - Streaming data: processing rows from a database or API page by page, like fetching IRCTC booking records in batches.
- Pipelines: chaining steps such as read lines, filter errors, parse fields, where each step pulls one item at a time.
- Infinite sequences: generating ticket numbers or IDs forever without running out of memory.
Common mistakes
- Looping over the same generator twice and getting nothing the second time.
- Calling
len()on a generator. It has no length; convert it withlist()if you really need one. - Expecting the function body to run when you call it. Nothing happens until you start iterating.
- Using a generator when you need random access like
items[5]. Use a list for that.
What to say in the interview
"A generator is a function with yield. Calling it returns a generator object, and each time you ask for the next value it runs until the next yield, hands out that value, and pauses with its local state saved. return ends the function for good and gives back one result, while yield can produce many values over time. The main benefit is memory: values are produced lazily, so I can process a huge file or an endless stream without loading it all. The trade-off is that a generator can only be iterated once and does not support indexing or len."
Follow-up questions
- What is the difference between a list comprehension and a generator expression?
- What happens if you call
next()on an exhausted generator? - What does
yield fromdo? - How is a generator related to an iterator?
Go deeper
Read these once the basics above feel easy.
Every generator is an iterator: it has __iter__ and __next__. Writing the same thing as a class needs you to store the state yourself and raise StopIteration manually; a generator does all of that for you.
yield from other_iterable delegates to another generator or iterable, yielding each of its values. It is handy for flattening nested structures or splitting a big generator into smaller ones.
Generators can also receive values with gen.send(value), which becomes the result of the yield expression inside. This is how coroutines worked before async/await, though you rarely need it in everyday code.