Advanced PythonAdvanced9 min54 / 63

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

Visual walkthrough1 / 6
1

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.

Think of it like a vending machine: one snack at a time, not the whole machine dumped on your lap.

#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.

This creates 1,000,000 numbers immediately and stores them all in RAM.
# 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])
Think of it like

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.

Each call to next() resumes the function from where it paused.
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.

Note

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.

The for loop pulls one value at a time — only five numbers ever exist in memory.
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.

Same result, but the generator version never holds all values in memory at once.
# 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 values
Tip

Generators 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.

This generator would keep going forever — we just stop asking for values.
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))
Watch out

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.

Advanced: generators can also receive values via .send(). The local variable 'total' persists across every pause.
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)
Common mistake

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.

Quick check

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.
Practice challenges
Test yourself · earn XP
0/4
Predict the output#1

What does this code print?

predict-output
def count_up(limit):
    n = 0
    while n < limit:
        yield n
        n += 1

gen = count_up(3)
print(next(gen))
print(next(gen))
Fix the bug#2

This code should print the squares 0, 1, 4, 9, 16 — but it prints nothing. What is wrong?

fix-bug
def squares(limit):
    for n in range(limit):
        return n * n

for value in squares(5):
    print(value)
Fill in the blank#3

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))
Reorder the lines#4

Put these lines in the right order to define a generator that yields the first n even numbers and then print them all.

1
    print(val)
2
    num = 0
3
        count += 1
4
    while count < n:
5
for val in even_numbers(4):
6
def even_numbers(n):
7
        yield num
8
    count = 0
9
        num += 2
Your turn
Practice exercise

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:

solution.py · editable