Back to Blog
Python

Python Yield vs Return: When to Use Each

python yield vs return: Understand the difference between yield and return in Python, how generators work, and when to choose each for memory-efficient code.

generatorsiteratorsfunction-returnpython-syntaxmemory-efficiency
A visual comparison of Python's yield and return, showing a generator producing values lazily versus a function returning a single result.

In Python, the choice between yield and return determines whether a function returns a single value or becomes a generator that produces a sequence of values lazily. The difference is not just syntactic; it changes how the function executes, how memory is used, and how callers interact with the result. This article explains the mechanics of python yield vs return, shows practical examples, and gives clear criteria for choosing one over the other.

How return Ends a Function and Returns a Value

A function with return executes its body, reaches a return statement, and immediately exits, passing the specified value back to the caller. Any code after the return is never executed. The function's local variables are discarded when the function exits. Each call to the function starts fresh.

def square(x): return x * x

Here, calling square(5) computes 25 and returns it. The function has no memory of previous calls.

How yield Converts a Function into a Generator

When a function contains at least one yield statement, it becomes a generator function. Calling the function does not execute the body; it returns a generator object. The body runs only when the generator is iterated, and it pauses at each yield, producing a value and saving its state.

def count_up_to(n): i = 1 while i <= n: yield i i += 1

Calling count_up_to(3) returns a generator object. Iterating over it produces 1, 2, 3. The function's state (the value of i and the position in the loop) is preserved between yields.

The State-Saving Behavior of Generators

The key difference is that a generator function suspends its execution at each yield and resumes later. This is different from a normal function that returns once and forgets everything. The generator's local variables and instruction pointer are stored in the generator object. This allows the generator to produce a potentially infinite sequence without exhausting memory.

For example, a generator that yields Fibonacci numbers indefinitely:

def fibonacci(): a, b = 0, 1 while True: yield a a, b = b, a + b

You can iterate over this generator with a for loop and break when you have enough values. A regular function would need to return a list, which would grow indefinitely.

Memory and Execution Differences

The most practical consequence is memory usage. A function that returns a list builds the entire list in memory before returning it. A generator produces one value at a time, so it uses constant memory regardless of how many items it yields.

Consider reading a large file line by line:

def read_lines(path): with open(path) as f: for line in f: yield line

This generator yields each line without loading the whole file. A version that returns a list would load all lines into memory, which can be problematic for large files.

Execution timing also differs. A regular function runs immediately and completely when called. A generator runs lazily: the code inside the generator body does not execute until the first next() call. This can be useful for delaying expensive computation until it is actually needed.

When to Use yield vs return

Use return when:

  • You need a single result from a function.
  • The result is small and can be computed eagerly.
  • The caller expects a concrete value, not an iterable.

Use yield when:

  • You want to produce a sequence of values.
  • The sequence is large or potentially infinite.
  • You want to avoid building a large list in memory.
  • You want to implement custom iteration behavior.

A common pattern is to write a generator function that yields values and then use it in a for loop or pass it to functions like sum() or list(). For example, sum(square(x) for x in numbers) uses a generator expression, which is a concise way to create a generator without a full function.

Combining yield and return in a Generator Function

A generator function can also contain a return statement, but it does not return a value to the caller in the usual sense. Instead, it raises StopIteration with the returned value. This is rarely used, but it can be useful for signaling a final value or for implementing coroutines.

def gen_with_return(): yield 1 yield 2 return "done"

Iterating over this generator yields 1 and 2, then raises StopIteration with the value "done". The for loop ignores this value, but you can catch it explicitly if needed.

Common Pitfalls and Misconceptions

One common mistake is assuming that calling a generator function executes the body immediately. It does not. If you need to validate arguments or perform setup, you must do it before the first yield or use a wrapper function.

Another pitfall is reusing a generator. Once a generator is exhausted, it cannot be restarted. If you need to iterate multiple times, you must create a new generator object.

Also, remember that yield can only be used inside a function. Using it in a lambda or a comprehension is not allowed.

python yield vs return: Practical Usage and Code Examples | RYUSLOG DEV