Hello! Welcome to the third lesson in our course.
Introduction
In the previous lesson, we implemented a custom iterator class, ReversedList. A key part of that implementation was using raise StopIteration within the __next__ method to signal that the iterator was exhausted.
This lesson focuses squarely on that mechanism. Our learning outcome is to explain the role of the StopIteration exception within the iterator protocol. We will explore why Python uses an exception for this purpose and, crucially, how constructs like for loops interact with it.
Understanding this is not just an academic exercise. It demystifies a core piece of Python's design, clarifies why iteration stops, and provides the conceptual foundation needed before we move on to generators, which handle this process for you automatically.
The Exception as a Control Signal
First, let's address the fundamental design choice. Why use an exception to signal the end of a sequence? Couldn't the __next__ method just return a special value, like None or False?
The problem with returning a special value is that it could also be a legitimate item in the sequence you're iterating over. If __next__ returned None to signal completion, you could never have an iterator that legitimately yields None as one of its values.
Python's solution is to use an "out-of-band" signal—a signal that can't be mistaken for data. The StopIteration exception serves this purpose perfectly. It's not an error in the traditional sense; it's a control signal that communicates "I have no more values" to the calling code.
The following article provides a detailed explanation of this concept.
How Python's Iterators and Iterables Work
This section from the article "How Python's Iterators and Iterables Work" delves into the purpose of StopIteration as a deliberate design feature for controlled termination.
Please read the section titled "The StopIteration Exception". Focus on its role as a mechanism for ending iteration in a controlled manner, distinguishing it from typical error-handling exceptions. Note the discussion on its design implications compared to alternative approaches.
Deconstructing the for Loop
The reason StopIteration doesn't feel like a typical, program-crashing exception is that for loops are designed to handle it automatically. A for loop is syntactic sugar for a more verbose process.
Let's look at a simple for loop:
my_list = [10, 20]
for item in my_list:
print(item)
This clean syntax hides the following machinery:
- It calls
iter(my_list)to get an iterator object. - It enters an internal loop.
- Inside the loop, it calls
next()on the iterator to get the next item. - It executes the loop body with that item.
- When
next()eventually raisesStopIteration, the loop catches this specific exception and breaks, terminating the iteration cleanly.
The following short video and diagram illustrate this "de-sugared" version of the for loop.
Python Tutorial: Iterators and Iterables - What Are They and How Do They Work?
This clip from Corey Schafer's tutorial provides a concise demonstration of what a for loop is actually doing behind the scenes.
Watch from 08:54 to 09:53. Pay close attention to the while True loop with the try...except StopIteration block, as this is the explicit equivalent of a for loop.
Here is the code equivalent that the video explains:
my_list = [10, 20]
# 1. Get the iterator
iterator = iter(my_list)
# 2. Start the loop
while True:
try:
# 3. Get the next item
item = next(iterator)
# 4. Execute the loop body
print(item)
except StopIteration:
# 5. Catch the signal and exit the loop
break
This try...except block is why you never see the StopIteration traceback when using a for loop. The loop is designed to listen for that specific signal and interpret it as "we're done here."
The following diagram provides a great visual summary of the entire protocol.

Practical Consequences and Patterns
Understanding this mechanism helps explain behavior in other contexts.
Manual Iteration
If you call next() manually on an exhausted iterator without the protection of a for loop or a try...except block, the exception will be raised and, if unhandled, will halt your program.
nums = iter([5])
print(next(nums)) # Output: 5
# This next call will raise an unhandled StopIteration exception
# print(next(nums))
# Traceback (most recent call last):
# ...
# StopIteration
The next() Function's default Argument
Sometimes, you want to get the next item or a default value if the iterator is exhausted, without writing a full try...except block. The built-in next() function has an optional second argument for this exact scenario.
next(iterator, default) will return default if the iterator is exhausted, instead of raising StopIteration.
my_iter = iter(['a', 'b'])
print(next(my_iter, 'END')) # Output: 'a'
print(next(my_iter, 'END')) # Output: 'b'
print(next(my_iter, 'END')) # Output: 'END' (no exception raised)
This pattern is a concise and Pythonic way to handle the end of iteration in specific cases. The following reading explores this.
Iterators and Iterables in Python: Run Efficient Iterations
The Real Python article you've seen before also covers this useful feature of the next() function.
Read the section "Using the Built-in next() Function", focusing on the explanation and example of the optional default argument.
The Role of StopIteration in Infinite Iterators
The role of StopIteration is most apparent when it's absent. Consider an iterator that never raises it. If used in a for loop, it will run forever because the loop's termination signal is never sent.
class CountUp:
def __init__(self, start=0):
self.current = start
def __iter__(self):
return self
def __next__(self):
value = self.current
self.current += 1
return value
# No 'raise StopIteration' condition!
counter = CountUp()
# This would be an infinite loop:
# for i in counter:
# print(i)
This demonstrates that StopIteration is not just a convention but the essential mechanism for terminating standard iteration in Python.
Conclusion
In this lesson, we've dissected the role of StopIteration, revealing it to be a clever and efficient control-flow mechanism, not an error.
Key Takeaways:
StopIterationis an exception used as an "out-of-band" signal to indicate the end of an iteration, avoiding collision with potential data values likeNone.- It is not an error but a fundamental part of the iterator protocol's control flow.
- High-level constructs like
forloops are built to automatically catchStopIterationto terminate the loop cleanly. This is syntactic sugar for awhile Trueloop with atry...except StopIterationblock. - Manually calling
next()on an exhausted iterator will raise an unhandledStopIterationunless you catch it or use thedefaultargument.
Next Up:
Now that we have a complete picture of the synchronous iterator protocol (__iter__, __next__, and StopIteration), we are ready to explore a more elegant way to create iterators. In the next lesson, we will learn about generator functions that use the yield keyword. You will see that they provide all this iterator machinery for you automatically.
Can't find a good explanation? Sign up and we'll make it for you
Sign up