yield marks a generator function. Calling that function does not run the whole body at once—it returns a generator object that produces values one step at a time. Each yield pauses the function, sends out a value, and saves local variables until the next step.
This page explains what yield means, a minimal example, how it differs from return, driving generators with next() and for, when generators help, and yield from. Generators are a specialized form of Python function—start with plain def and return before leaning on lazy iteration. Advanced topics such as send(), async generators, and threading are out of scope here; see the official generator tutorial when you need those.
Tested on: Python 3.13.3; kernel 6.14.0-37-generic.
What does yield mean in Python?
In a normal function, return ends execution and hands back a result. With yield, the function becomes a generator:
- The first call returns a generator object (the body has not run yet, beyond creating that object).
- Each
next()call (or each step of aforloop) runs the function until the nextyield. - The function pauses at
yield, delivers a value, and keeps its local state. - When the function falls off the end or hits
return, iteration stops withStopIteration.
def count_up_to(max):
count = 1
while count <= max:
yield count
count += 1
counter = count_up_to(3)
print(type(counter))
print(next(counter))
print(next(counter))You should see <class 'generator'>, then 1, then 2. A third next(counter) would print 3; one more raises StopIteration.
A generator is lazy: values are computed when requested, not all upfront in a list. That matters for long sequences or pipelines, but the core idea to remember first is pause, produce, resume—not performance tuning.
Python yield example
A generator with several yield statements produces values in order:
def greet_people():
yield "Hello, John"
yield "Hello, Jane"
yield "Hello, Jim"
for greeting in greet_people():
print(greeting)You should see three greeting lines. The function runs in chunks: it starts, hits the first yield, stops; the loop asks for the next value; execution picks up after that yield, and so on.
Another common shape is yield inside a loop when you do not want to build a full list in memory:
def first_n_squares(n):
for x in range(n):
yield x * x
print(list(first_n_squares(5)))You should see [0, 1, 4, 9, 16]. Here we materialize with list() for display; in real code you often keep the generator and iterate once.
yield vs return in Python
return |
yield |
|
|---|---|---|
| Function type | Normal function | Generator function |
| On call | Runs to completion (or first return) |
Returns a generator object |
| Result | One value (or a tuple / list you return) | Many values over time |
| State | Lost after the function exits | Preserved between steps |
| Typical use | Compute and give back a final answer | Stream or sequence of values |
def make_list():
return [1, 2, 3]
def make_gen():
yield 1
yield 2
yield 3
print(make_list())
print(type(make_gen()))
print(list(make_gen()))You should see [1, 2, 3], then <class 'generator'>, then [1, 2, 3] again from the generator.
return inside a generator still ends iteration (optionally with a value attached to StopIteration in Python 3.3+). You cannot mix “return a list of everything” and “yield items one by one” in the same function for the same purpose—pick one model per function.
State preservation is the behavioral difference people notice first:
def count_to_two():
print("before first yield")
yield 1
print("before second yield")
yield 2
g = count_to_two()
print(next(g))
print(next(g))You should see before first yield, 1, before second yield, 2. Code after a yield runs only on later steps—not on the initial call that created g.
How generators work with next() and for loop
next(gen) advances the generator by one step. When there is nothing left, Python raises StopIteration:
def three_values():
yield "a"
yield "b"
yield "c"
g = three_values()
print(next(g))
print(next(g))
print(next(g))
try:
next(g)
except StopIteration:
print("done")You should see a, b, c, then done.
A for loop does the same thing under the hood: it calls next() repeatedly until StopIteration. That is why you write the loop shown below instead of managing next() yourself. For loop syntax and for / else, see Python for loop.
for value in three_values():
print(value)Every generator is an iterator: you get one pass. After exhaustion, a second loop over the same object produces nothing:
g = (x for x in range(3))
print(list(g))
print(list(g))You should see [0, 1, 2] then []. Generators do not support len() without consuming them—another sign they are streams, not stored sequences.
When to use yield in Python
Use a generator when you want to produce a sequence lazily—one item at a time—without building a large list first.
Good fits:
- Stepping through a long or unbounded sequence (
range-like logic, Fibonacci, paginated chunks). - Piping data through stages (read → filter → transform) where each stage yields rows.
- A one-off iterable from a generator expression
(x * x for x in range(n))instead of a list comprehension when you only iterate once.
print(sum(x * x for x in range(5)))You should see 30. Parentheses make a generator expression; square brackets would build a list first.
Reach for a normal function with return when you need random access, repeated traversals, or a concrete collection to index and slice. If you call a generator function expecting a list, you only get a generator object—wrap with list(...) when you truly need all values in memory.
Common mistakes:
- Calling
make_gen()and treating the result like a list without iterating. - Expecting a second
forloop over the same generator to replay values. - Using
yieldin a short function wherereturn [ ... ]is clearer and small enough to fit in memory.
For writing many lines to disk, generators often pair with file loops; see Python write to file for the file side. This page stays focused on yield itself.
Python yield from
yield from delegates to another generator or iterable. Python pulls every value from the sub-iterator for you—handy for recursive flattening:
def flatten(nested):
for item in nested:
if isinstance(item, list):
yield from flatten(item)
else:
yield item
print(list(flatten([1, [2, [3, 4], 5]])))You should see [1, 2, 3, 4, 5]. Without yield from, the inner call would need an explicit loop:
for sub in flatten(item):
yield subyield from also forwards send() and throw() in advanced code; for most scripts, read it as “yield everything from this other generator.” Details are in PEP 380.
Summary
yield turns a function into a generator that pauses at each yield, produces one value, and resumes later with local state intact. Calling the function returns a generator object; next() and for drive it until StopIteration. Use return when you need a single finished result; use yield when you want a lazy sequence. Generators run once—replay requires a new generator or a list. yield from delegates to another iterable, which simplifies nested patterns like flattening. For deeper material (send, async generators), use the Python docs on generators.

