StopIteration in Python: the end of an iterator

StopIteration is the end signal of a Python iterator. What the for loop does with it, and why it should never be raised by hand.
5 min read
Believemy logo

Definition

When items are pulled one by one from a source, with next(), there has to be a way to know when none are left. Returning None would not work: nothing stops None from being a legitimate value in the source, and confusing a real end with an ordinary piece of data would break the code on the very first edge case. What was needed was a signal that could never be mistaken for whatever was being walked through.

StopIteration is that signal: the exception an iterator raises once it has nothing left to hand out. It is not a fault but a convention, the one a source uses to announce it has reached the end. Every call to next() consumes an item and removes it on the way; when none are left, the following call raises StopIteration instead of inventing a value.

On a list of two letters, this is what it looks like:

PYTHON
letters = iter(["a", "b"])

next(letters)  # 'a'
next(letters)  # 'b'
next(letters)

# StopIteration

The source may be a list, a tuple, a string, an open file or a generator: the protocol stays the same across all of them. That sameness is what lets a single loop walk through objects of very different natures without ever changing how it is written.


The for loop already catches it

This exception is rarely seen in practice: the for loop intercepts it before it reaches the rest of the program. Writing for item in collection: amounts to asking for an iterator, calling next() in a loop, then leaving cleanly as soon as the end signal shows up. The reader never has to handle StopIteration directly, the loop has already done it on their behalf.

Here is what that syntax hides:

PYTHON
# What a for loop really does
iterator = iter(collection)

while True:
    try:
        item = next(iterator)
    except StopIteration:
        break
    handle(item)

That equivalence explains a behaviour that often surprises: an iterator already walked through cannot be walked again. Once the inner while has reached StopIteration, there is nothing left to consume, so a second for loop over the same object produces nothing at all, with no error shown anywhere.

It also explains why tools built on this protocol, such as enumerate or zip, stop at the very first end signal received, without waiting for the other sources to empty out. As soon as one of the combined sources raises StopIteration, they stop immediately, even if another still has items left.


Three ways to handle the end

A for loop absorbs the signal on the reader's behalf. But what happens when next() is called directly, outside any loop? That is the only situation where the exception truly reaches the screen, and three writings answer it, depending on what the end of the source means for the program.

The table below sums them up, from the strictest to the most forgiving:

WritingBehaviour on an exhausted source
next(iterator)Raises StopIteration and stops the program
next(iterator, None)Hands back the default value, raising nothing
try around the callAllows the end to be handled as a case of its own

The second argument of next is generally the most practical of the three: it turns the exception into an ordinary value, sparing three lines of code from being wrapped in a recovery block for a case that is entirely predictable. The try block earns its keep when the end needs to trigger a treatment of its own, such as logging the event rather than simply carrying on with a default value.


The generator trap

A generator, meaning a function holding a yield, also raises StopIteration once it reaches the end of its work. That raises a question: what happens to a return written in its body, since an ordinary function hands back a value with that very keyword? Nothing of the sort here: a generator's return does not hand back a value, it ends the production, nothing more.

Warning

Before Python 3.7, a StopIteration raised by mistake from inside a generator, a forgotten next() in its body, for instance, used to end the calling loop without the slightest message, and the missing data went unnoticed for months. Since that version, this case is converted into a RuntimeError, so the error can no longer hide.

The main thing to remember is this: never raise StopIteration by hand to interrupt a process. break stops a loop, return stops a function, and one of the two is always enough.


Frequently asked questions

Question

Should StopIteration be caught in ordinary code?

Only around a direct call to next(), and even then the default value does the same job in a single line. Everywhere else the loop already handles it, and catching the exception oneself amounts to rewriting what the language already does perfectly well on its own.

Question

Why does a second loop over the same object give nothing?

Because an iterator does not rewind. A list hands back a fresh one on every pass, which makes it walkable indefinitely, whereas a generator or an open file is the iterator itself: once exhausted, it stays that way for good.

Question

How does it differ from an ordinary program error?

This one describes a normal course of events, not a breakdown. It belongs to a different branch than value or type errors, and catching too broadly risks mistaking it for a genuine defect, hiding both at once.

Related terms

Discover our python glossary

Browse the terms and definitions most commonly used in development with Python.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.