What Is yield in Python? Generators Explained

So what is yield in Python? It’s a keyword that turns a function into a generator: instead of returning one result and ending, the function hands back a value, pauses, and picks up where it left off the next time you ask for a value:

def countdown(n):
    while n > 0:
        yield n
        n -= 1

list(countdown(3))   # [3, 2, 1]

The prints inside each generator below show exactly when its code runs. I ran them all on Python 3.12.5.

We’ll trace how a generator pauses, loop over one, compare memory with a list, process a big file lazily, build an infinite sequence, and look at yield from and send().

How yield works: a generator step by step

Calling a generator function doesn’t run it; you get a generator object. Each next() runs the body until the next yield, then pauses there:

def countdown(n):
    print("  (starting)")
    while n > 0:
        yield n                # hand back a value and pause right here
        n -= 1
    print("  (finished)")

gen = countdown(3)
print(type(gen).__name__)       # calling it didn't run the body yet
print(next(gen))
print(next(gen))
print(next(gen))
try:
    next(gen)
except StopIteration:
    print("StopIteration: nothing left")

Output:

generator
  (starting)
3
2
1
  (finished)
StopIteration: nothing left
Command Prompt output of a Python generator function printing starting, yielding 3, 2 and 1 one at a time, then raising StopIteration
“(starting)” appears only at the first next(). When the function ends, the generator raises StopIteration.

Local variables like n survive between calls, because the function’s frame is frozen at the yield, not thrown away.

Using a generator in a for loop

A for loop calls next() for you and stops cleanly at StopIteration. Just remember a generator can be consumed only once:

def even_prices(prices):
    for p in prices:
        if round(p * 100) % 2 == 0:
            yield p

menu = [4.50, 2.75, 3.20, 9.99, 12.00]
for price in even_prices(menu):          # for loops call next() for you
    print(f"${price:.2f}")

gen = even_prices(menu)
print(list(gen))                          # first pass uses it up...
print(list(gen))                          # ...second pass is empty

Output:

$4.50
$3.20
$12.00
[4.5, 3.2, 12.0]
[]

If you need the values twice, call the generator function again or store them with list(). The loop side is covered in the Python for loop guide.

Why use yield instead of returning a list?

A list holds every value at once. A generator produces them one at a time, so its size stays tiny no matter how many values it will produce:

import sys

squares_list = [n * n for n in range(1_000_000)]
squares_gen = (n * n for n in range(1_000_000))         # generator expression

print(f"list:      {sys.getsizeof(squares_list):>10,} bytes")
print(f"generator: {sys.getsizeof(squares_gen):>10,} bytes")
print("same total:", sum(squares_list) == sum(n * n for n in range(1_000_000)))

Output:

list:       8,448,728 bytes
generator:        200 bytes
same total: True
Command Prompt output comparing the memory of a Python list of one million squares with an equivalent generator expression
About 8 MB for the list versus about 200 bytes for the generator, with the same sum.

(n * n for n in ...) with parentheses is a generator expression, the one-line form of a generator function. See Python for loop in one line for more.

Processing a large file with yield

This is where I find generators shine in real work. Reading a file line by line inside a generator keeps memory flat even for files with millions of rows:

with open("orders_2026.csv", "w", encoding="utf-8") as f:
    f.write("id,city,total\n")
    for i in range(1, 100_001):
        f.write(f"{i},{'Austin' if i % 3 else 'Denver'},{i % 50 + 0.99}\n")

def read_orders(path):
    with open(path, encoding="utf-8") as f:
        next(f)                                    # skip the header
        for line in f:                             # one line in memory at a time
            order_id, city, total = line.rstrip().split(",")
            yield city, float(total)

denver = sum(total for city, total in read_orders("orders_2026.csv") if city == "Denver")
print(f"Denver revenue: ${denver:,.2f}")

Output:

Denver revenue: $849,682.67
Command Prompt output of a Python generator reading a 100,000-row orders CSV lazily and summing Denver revenue in USD
Only one line is in memory at a time, yet the total covers all 100,000 orders.

The with block stays open while the generator runs and closes when it finishes. More on that in Python with open() as file.

Infinite generators and yield from

A generator can loop forever, because it only does work when asked. itertools.islice() takes just the first few values. yield from passes along everything from another iterable:

from itertools import islice

def invoice_numbers(start=1001):
    n = start
    while True:              # infinite, but only runs as far as you ask
        yield f"INV-{n}"
        n += 1

print(list(islice(invoice_numbers(), 5)))

def chain_all(*iterables):
    for it in iterables:
        yield from it        # delegate to another iterable

print(list(chain_all(["TX", "CA"], ("NY",), ["AZ"])))

Output:

['INV-1001', 'INV-1002', 'INV-1003', 'INV-1004', 'INV-1005']
['TX', 'CA', 'NY', 'AZ']
Command Prompt output of an infinite Python invoice number generator sliced with islice, and yield from chaining several iterables
Five invoice numbers from a generator that never ends, and four states chained with yield from.

A while True loop is safe here because the caller decides when to stop. See infinite loops in Python for the regular kind.

Sending values into a generator

yield can also receive values. send() resumes the generator and makes the yield expression evaluate to what you sent, which suits running calculations:

def running_average():
    total, count = 0.0, 0
    average = None
    while True:
        value = yield average          # receive a value from send()
        total += value
        count += 1
        average = total / count

avg = running_average()
next(avg)                              # prime it: run to the first yield
for sale in [120.00, 80.00, 100.00]:
    print(f"sale ${sale:.2f} -> running average ${avg.send(sale):.2f}")

Output:

sale $120.00 -> running average $120.00
sale $80.00 -> running average $100.00
sale $100.00 -> running average $100.00

In my own code, send() shows up far less often than plain iteration, but it’s handy for stateful pipelines like this running average.

yield vs return

returnyield
Function becomesA normal functionA generator function
Values producedOne, then the function endsMany, one per next()
State between callsLostKept (paused frame)
Memory for many valuesNeeds a full listConstant
Can be looped twiceYes (the list)No, it’s used up

More on functions and iteration in Python:

Frequently asked questions

What is yield in Python?

It’s a keyword that makes a function a generator. Each yield hands a value to the caller and pauses the function until the next value is requested.

What’s the difference between yield and return?

return sends back one value and ends the function. yield sends back a value and pauses, so the function can produce many values over time.

How do I get all values from a generator?

Loop over it, or call list(gen). Remember it can only be consumed once.

What is StopIteration?

The signal a generator raises when it has no more values. for loops handle it automatically.

What does yield from do?

It yields every value from another iterable or generator, so you don’t need a nested loop.

Where is yield documented?

See Yield expressions in the Python language reference.