Python Iterators & Generators
Learn the iterator protocol, build custom iterators, and use generators with yield.
The Iterator Protocol
Every for loop in Python secretly uses the iterator protocol.
When you loop over a list, Python calls iter() to get an iterator object, then repeatedly
calls next() on it to get each value. A StopIteration exception signals
the end. You can do this manually too — understanding it helps you write custom iterables.
nums = [10, 20, 30] it = iter(nums) # get an iterator print(next(it)) # first value print(next(it)) # second value print(next(it)) # third value # next(it) now would raise StopIteration
10 20 30
Custom Iterator Class
Any class implementing __iter__ (returns self) and __next__ (returns
next value or raises StopIteration) can be used in a for loop.
class CountUp: def __init__(self, start, stop): self.n = start; self.stop = stop def __iter__(self): return self def __next__(self): if self.n > self.stop: raise StopIteration val = self.n; self.n += 1; return val for n in CountUp(1, 5): print(n, end=" ")
1 2 3 4 5
Generators — A Simpler Way
Generators are much easier to write than full iterator classes. Use yield instead of
return in a function. Each call to next() runs the function until the next
yield, then pauses, saving the function's state. This is
lazy evaluation — values are created on demand, not all at once.
def fibonacci(n): a, b = 0, 1 for _ in range(n): yield a # pause here, return a a, b = b, a + b # resume here next call for num in fibonacci(8): print(num, end=" ")
0 1 1 2 3 5 8 13
🧠 Quick Check
Which keyword makes a function into a generator?