RuntimeError: generator raised StopIteration (tested)

RuntimeError: generator raised StopIteration means a StopIteration, almost always from a bare next(), escaped from inside a generator. We measured why that's an error since Python 3.7, the fixes, and the related case where nothing is raised at all.

1. What triggers it

def first_words(lines):
    for line in lines:
        it = iter(line.split())
        yield next(it)              # empty line -> StopIteration inside a generator

try:
    print(list(first_words(["hello world", "", "bye"])))
except RuntimeError as e:
    print(type(e).__name__ + ":", e)
    print("cause:", repr(e.__cause__))

Output:

RuntimeError: generator raised StopIteration
cause: StopIteration()

Measured on Python 3.13.5: a bare next() inside a generator ran out (here, on an empty line) and the StopIteration escaped from the generator body. Since Python 3.7 (PEP 479), Python turns that into RuntimeError: generator raised StopIteration; the original StopIteration is kept as __cause__, so the traceback shows the next() line under "The above exception was the direct cause".

2. Old code that used next() to end the generator

def pairs(it):
    it = iter(it)
    while True:
        a = next(it)                # pre-3.7 this quietly ended the generator
        b = next(it)
        yield a, b

try:
    print(list(pairs([1, 2, 3, 4])))
except RuntimeError as e:
    print("RuntimeError:", e)

def pairs_fixed(it):
    it = iter(it)
    while True:
        try:
            a = next(it)
            b = next(it)
        except StopIteration:
            return                  # end the generator explicitly
        yield a, b

print(list(pairs_fixed([1, 2, 3, 4])))
print(list(pairs_fixed([1, 2, 3, 4, 5])))   # odd item dropped
print(list(zip(*[iter([1, 2, 3, 4, 5])] * 2)))

Output:

RuntimeError: generator raised StopIteration
[(1, 2), (3, 4)]
[(1, 2), (3, 4)]
[(1, 2), (3, 4)]

Measured: before 3.7 a StopIteration from next() quietly ended the generator, and a lot of older code (and Stack Overflow answers) relied on that. Now you have to end it yourself: catch StopIteration and return. For this particular job, zip(*[iter(xs)] * 2) or itertools.batched(xs, 2) (3.12+) do it without a generator; our fixed version and zip both drop the odd last item, while batched keeps it as (5,) (checked separately).

3. Fix: next() with a default, and return instead of raise

def first_words(lines):
    for line in lines:
        w = next(iter(line.split()), None)   # default instead of StopIteration
        if w is not None:
            yield w

print(list(first_words(["hello world", "", "bye"])))

def gen():
    yield 1
    raise StopIteration         # never do this; use return
try:
    print(list(gen()))
except RuntimeError as e:
    print("raise StopIteration:", e)

Output:

['hello', 'bye']
raise StopIteration: generator raised StopIteration

next(iterator, None) returns the default instead of raising, which is usually what you meant. To finish a generator early, write return; raise StopIteration inside a generator is now always the RuntimeError above.

4. Outside generators: map() silently truncates

def first(xs):
    return next(iter(xs))           # StopIteration on an empty list

# In a plain call it escapes as StopIteration:
try:
    first([])
except StopIteration as e:
    print("plain call:", repr(e))

# Inside map()/filter(), the SAME exception silently ends the iteration:
rows = [[1], [], [3]]
print("map:", list(map(first, rows)))
# A generator expression converts it to RuntimeError instead:
try:
    print(list(first(r) for r in rows))
except RuntimeError as e:
    print("genexp:", e)
# A list comprehension lets the StopIteration escape:
try:
    print([first(r) for r in rows])
except StopIteration:
    print("listcomp: StopIteration escaped")

Output:

plain call: StopIteration()
map: [1]
genexp: generator raised StopIteration
listcomp: StopIteration escaped

Measured: PEP 479 only covers generators (and generator expressions). The same StopIteration raised by a function called from map() or filter() just ends the iteration: we got [1] from three rows with no error, losing the third row. A list comprehension lets it escape as a plain StopIteration. If a helper calls next(), give it a default or catch StopIteration inside the helper.

Frequently asked questions

What does RuntimeError: generator raised StopIteration mean?

A StopIteration escaped from inside a generator, usually from a bare next() on an exhausted iterator. Since Python 3.7 (PEP 479) that becomes a RuntimeError; we measured it with the original StopIteration as __cause__.

How do I fix generator raised StopIteration?

Use next(it, default) or wrap next() in try/except StopIteration and return. Replace raise StopIteration with return. We measured both fixes on Python 3.13.5.

Why did my code work in Python 3.6 but not 3.7+?

Before 3.7, a StopIteration inside a generator silently ended it. PEP 479 made that an error by default in 3.7; we measured old-style pairing code raising RuntimeError on 3.13.

Can StopIteration cause silent data loss?

Yes, outside generators. We measured a function raising StopIteration inside map() ending the iteration early: three rows in, one result out, no error.

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5 on Linux: bare next() in a generator, old-style pairing code, the next(it, None) and return fixes, raise StopIteration in a generator, and the same StopIteration under map(), a generator expression and a list comprehension. Every block was run verbatim and its output pasted from that run. Pre-3.7 behaviour is from PEP 479, not re-tested. PlainHeaders is our extension; everything else here is built-in Python.