HomeGuidesPythonPython Generators Explained — yield, next() & Memory Efficiency
🐍 Python

Python Generators: yield, Lazy Evaluation & When to Use Them

Generators produce values on demand instead of all at once — making them essential for large data and infinite sequences. Here's what exams actually test.

Examifyr·2026·5 min read

yield vs return

A function with `yield` becomes a generator function. Calling it returns a generator object — it does not execute the body immediately. Each `next()` call resumes from where it last yielded.

def countdown(n):
    while n > 0:
        yield n       # pauses here, returns n
        n -= 1

gen = countdown(3)    # does NOT run the body yet
print(next(gen))      # 3
print(next(gen))      # 2
print(next(gen))      # 1
# next(gen) now raises StopIteration
Note: Calling a generator function returns a generator object. The body runs only when you call next() on it.

Iterating generators

Generators implement the iterator protocol, so you can use them directly in for loops, list(), sum(), and any function that accepts an iterable.

def squares(n):
    for i in range(n):
        yield i ** 2

# for loop automatically calls next() until StopIteration
for s in squares(5):
    print(s)          # 0, 1, 4, 9, 16

# Convert to list (consumes the generator)
result = list(squares(5))  # [0, 1, 4, 9, 16]

total = sum(squares(5))    # 30
Note: Generators can only be iterated once. After exhaustion, they're empty — create a new generator object to iterate again.

Generator expressions

Generator expressions use parentheses instead of brackets. They're the lazy equivalent of list comprehensions.

# List comprehension — builds entire list immediately
lst = [x**2 for x in range(1_000_000)]   # ~8 MB

# Generator expression — produces one value at a time
gen = (x**2 for x in range(1_000_000))   # ~200 bytes

# Both work the same way in a for loop or sum()
total = sum(x**2 for x in range(1_000_000))

Infinite generators

Because generators are lazy, they can represent infinite sequences. You control how many values you consume.

def integers_from(n):
    while True:       # infinite loop — fine in a generator
        yield n
        n += 1

gen = integers_from(1)
print(next(gen))  # 1
print(next(gen))  # 2

# Take first 5 using itertools.islice
import itertools
first_five = list(itertools.islice(integers_from(1), 5))
print(first_five)  # [1, 2, 3, 4, 5]

yield from — delegating to sub-generators

`yield from` delegates to another iterable or generator, forwarding values without a manual loop.

def chain(*iterables):
    for it in iterables:
        yield from it    # delegates each item from the iterable

result = list(chain([1, 2], [3, 4], [5]))
print(result)  # [1, 2, 3, 4, 5]
Note: `yield from iterable` is equivalent to `for item in iterable: yield item` — but shorter and faster.

Exam tip

Key exam questions: (1) a generator can only be iterated once — calling list() on it a second time returns []; (2) the body doesn't execute until next() is called; (3) StopIteration is raised automatically when the generator function returns.

🎯

Think you're ready? Prove it.

Take the free Python readiness test. Get a score, topic breakdown, and your exact weak areas.

Take the free Python test →

Free · No sign-up · Instant results

← Previous
Python Decorators Explained — @syntax, functools.wraps & Stacking
Next →
Python Lambda Functions Explained — map, filter, sorted & Limits
← All Python guides