Generators & yield
Learn how generator functions use yield to produce values one at a time, saving memory and unlocking powerful lazy computation patterns.
Imagine you ask a librarian for every book ever written. She could spend years collecting them all into a giant pile — or she could hand you one book at a time as you ask for the next one. That second approach is exactly what a generator does in Python.
A generator is a special kind of function that produces values lazily — one at a time, only when you ask. This saves a huge amount of memory and makes your programs faster when working with large or even infinite sequences.
See it in action
— step through the idea, then dive into the details below.Values On Demand, Not All At Once
A generator is a function that hands you values one at a time — only when you ask. Instead of building a giant list in memory, it lazily produces each item on demand.
#The problem with building big lists
Before generators, if you wanted to work with a million numbers, you'd build a list with all million items sitting in memory at once. For small data that's fine. For large data, it's wasteful — or even impossible.
# Building a full list — everything in memory at once
numbers = [x * x for x in range(1_000_000)]
print(numbers[0])
print(numbers[1])The vending machine analogy
A list is like buying every snack in the vending machine upfront and carrying them all home. A generator is like the machine itself — it gives you one snack on demand, and you leave the rest inside until you need them.
#Your first generator function
A generator function looks almost identical to a regular function, but instead of return it uses the yield keyword. Every time Python hits a yield, it pauses the function, hands back that value, and remembers exactly where it left off for next time.
def count_up(limit):
n = 0
while n < limit:
yield n # pause here, hand out n
n += 1 # resume here next time
gen = count_up(3)
print(next(gen))
print(next(gen))
print(next(gen))Notice that calling count_up(3) does not run the body immediately. It just creates a generator object. The body only runs when you ask for the next value with next() or loop over it.
What happens when values run out?
If you call next() after all values have been yielded, Python raises a StopIteration exception. A for loop catches this automatically and stops cleanly — you usually don't need to worry about it.
#Looping over a generator with for
You almost never call next() manually. A for loop handles it for you automatically, asking for the next value until the generator is exhausted.
def squares(limit):
for n in range(limit):
yield n * n
for value in squares(5):
print(value)#Generator expressions — the compact shortcut
Just like list comprehensions let you write [x*x for x in range(n)], you can write a generator expression by swapping the square brackets for parentheses. The result is lazy — no values are computed until you ask for them.
# List comprehension — builds all values right now
list_version = [x * x for x in range(5)]
print(list_version)
# Generator expression — builds values on demand
gen_version = (x * x for x in range(5))
print(gen_version) # shows object, not values
print(list(gen_version)) # consume it to see all valuesGenerators work great with sum(), max(), min()
Many built-in functions accept any iterable, so you can pass a generator expression directly without converting to a list:
``python total = sum(x * x for x in range(1_000_000)) ``
This adds a million squares without ever building a million-item list.
#Infinite generators
Because generators produce values lazily, they can represent sequences that never end — something impossible with a plain list. You just need to stop consuming them at some point.
def count_forever(start=0):
n = start
while True: # runs forever...
yield n
n += 1
counter = count_forever(10)
print(next(counter))
print(next(counter))
print(next(counter))Never do list(infinite_generator)
If you try to convert an infinite generator to a list with list(count_forever()), Python will try to collect infinite values and your program will freeze or crash. Always use next(), for with a break, or limit how many values you take.
#A generator remembers its state
One of the most powerful things about generators is that local variables inside the function stay alive between yields. The function is truly paused, not restarted from scratch each time.
def running_total():
total = 0
while True:
amount = yield total # yield sends out total, receives the next amount
if amount is not None:
total += amount
bank = running_total()
next(bank) # start the generator
bank.send(100)
bank.send(50)
result = bank.send(25)
print(result)A generator can only be iterated once
Once a generator is exhausted, it's done. Looping over it again produces nothing:
``python gen = squares(3) print(list(gen)) # [0, 1, 4] print(list(gen)) # [] — already exhausted! ``
If you need to iterate multiple times, either store values in a list or create a new generator object each time.
#When to use a generator
Choose a generator when: - The sequence is very large (millions of items) and you don't need them all at once. - The sequence is infinite (like a live data stream). - You only need to iterate once and don't need random access by index. - You want to chain processing steps without building intermediate lists.
Stick with a plain list when: - The data is small and you need to access items by index. - You need to iterate over the same data multiple times. - You need to know the length upfront.
What does the yield keyword do inside a generator function?
Key takeaways
- A generator function uses yield instead of return to produce values one at a time without storing them all in memory.
- Generator expressions use parentheses (x*x for x in range(n)) and are the lazy, memory-efficient version of list comprehensions.
- Call next() to pull the next value manually, or use a for loop to consume values automatically.
- Generators can represent infinite sequences because values are computed on demand — just avoid converting them to a list.
- A generator is exhausted after one full iteration; create a fresh generator object if you need to iterate again.
What does this code print?
def count_up(limit):
n = 0
while n < limit:
yield n
n += 1
gen = count_up(3)
print(next(gen))
print(next(gen))This code should print the squares 0, 1, 4, 9, 16 — but it prints nothing. What is wrong?
def squares(limit):
for n in range(limit):
return n * n
for value in squares(5):
print(value)Complete the generator expression so it works like a lazy version of the list [0, 1, 4, 9, 16] and the final print shows the total 30.
gen = (x * x for x in (5)) print(sum(gen))
Put these lines in the right order to define a generator that yields the first n even numbers and then print them all.
print(val)
num = 0
count += 1
while count < n:
for val in even_numbers(4):
def even_numbers(n):
yield num
count = 0
num += 2
Write a generator function called fibonacci() that yields Fibonacci numbers indefinitely (0, 1, 1, 2, 3, 5, 8, 13, ...). Then use it to print the first 10 Fibonacci numbers.
Try it live — edit the code and hit Run to execute real Python: