Question bank — Iterators — `__iter__`, `__next__`, StopIteration
The three symbols, refreshed before we use them
Everything on this page leans on exactly three pieces of vocabulary. So that a reader landing here in isolation never meets a symbol undefined, here they are in plain words:
Keep the picture below in mind for every trap — the tape player (iterator) has one button (next), pressing past the end gives the click (StopIteration), and a list is a bag that hands you a fresh player each time you ask (iter).

The iterable vs iterator split — a bag versus the dispenser it produces — is the single idea that most traps below hinge on:

True or false — justify
Every iterable is also an iterator.
__iter__ (it can give a dispenser); a list is iterable but has no __next__, so you cannot press its button directly — next([1,2]) raises TypeError. Iterators are the narrower kind that also answer next().Every iterator is also an iterable.
__iter__ returning self, so for and iter() accept it directly.An iterator's __iter__ must return a brand-new object.
self== — literally the same object, so it.__iter__() is it is True. Returning a new one would abandon the current position and break the protocol.A reusable iterable's __iter__ should return self.
obj.__iter__() is obj.__iter__() is False — each for loop gets its own independent position.Calling iter() on a list twice gives two independent positions.
a = iter([1,2]); b = iter([1,2]) → a is b is False; advancing a with next(a) leaves b untouched. That independence is the whole reason iterable and iterator are separate roles.Calling iter() on an iterator gives a new independent position.
it2 = iter(it) → it2 is it is True, so both names share one cursor — next(it2) also advances it.StopIteration is an error your code did something wrong to trigger.
for deliberately wraps next() in try/except StopIteration: break. Nothing went wrong — the loop just heard the click.Once an iterator raises StopIteration, pressing next() again resets it.
StopIteration forever; it never magically refills. To restart, you must build a new iterator.An infinite iterator is impossible because you can't store infinite items.
__next__ computes one item on demand and stores only its current state (e.g. self.n), so it never needs the whole (infinite) sequence in memory.next(it, default) and next(it) behave the same when the iterator is empty.
next(it) raises StopIteration; next(it, default) catches it internally and returns default instead — see the "why" below for the reason both forms exist.You can loop over the same list twice and get all elements both times.
for calls iter(list) afresh, so a new cursor starts at the beginning each loop.You can loop over the same iterator twice and get all elements both times.
An object with no __iter__ can never be looped over.
__iter__ is missing but __getitem__ exists, iter() builds an iterator that calls obj[0], obj[1], ... until IndexError, which it treats like StopIteration. See the edge-case section.Spot the error
class C: with only __next__ and no __iter__ (and no __getitem__) — what breaks?
for/iter() call __iter__ first, and with no __getitem__ fallback either, you get TypeError: object is not iterable. Add __iter__ returning self.In __next__, using return instead of raise StopIteration to stop.
return just hands back a value (often None); the loop never learns the tape ended and runs forever or yields Nones. You must raise StopIteration.__next__ advances state before the exhaustion check, e.g. decrements then checks <= 0.
0) that should have been excluded. The exhaustion check must come first, before computing/returning.__iter__ returns iter(self.data) in a class meant to be its own iterator with __next__.
for uses the data's iterator and never touches your __next__; your carefully written logic is silently bypassed. Return self if the object is its own iterator.A class stores no reset logic, returns self from __iter__, and is looped twice expecting a full run each time.
self.current at its end value) and yields nothing. For reuse, return a fresh iterator from __iter__ instead of self.Inside a generator, doing raise StopIteration to end early.
StopIteration becomes a RuntimeError when it escapes the generator. In generators use return; only hand-written classes raise StopIteration. See StopIteration and PEP 479.A __next__ that computes the value but forgets to advance self.current.
__next__ must move the state forward before or after returning.Why questions
Why does for need try/except StopIteration rather than checking a "hasNext" method?
for works uniformly over every iterator without asking each one to also implement lookahead. The exception is the "no more" answer.Why must an iterator's __iter__ return self and not, say, a copy?
for or iter() continues from where it is, rather than silently restarting or duplicating state.Why does next(it, default) exist at all when next(it) already works?
try/except StopIteration boilerplate. The one-argument form raises so that loops and callers who do need to know about exhaustion aren't handed a silent sentinel by accident. You pick raising vs. defaulting based on whether emptiness is exceptional or expected.Why separate "iterable" from "iterator" at all — why not merge them?
Why do generators feel simpler than writing __iter__/__next__ by hand?
__iter__ returns self, yield produces items, and hitting the end raises StopIteration for you. See Generators — yield, lazy evaluation.Why is list(infinite_iterator) dangerous but next(infinite_iterator) fine?
list() keeps pressing next until StopIteration, which never comes, so it hangs. next() presses exactly once. Use itertools.islice to take a finite slice safely.Why is laziness (one item at a time) the point, not a side effect?
Edge cases
What does iter([]) then next() do?
iter([]) succeeds and returns an empty iterator; the first next() immediately raises StopIteration since there is nothing to hand back.What does for x in EmptyIterable(): execute?
for gets the iterator, the first next raises StopIteration, and the loop breaks before running the body once.If __next__ raises StopIteration on the very first call, is that valid?
for simply runs its body zero times; nothing is wrong.What happens if you call next() twice past the end of a finite iterator?
StopIteration; the iterator does not reset, refill, or start erroring differently. Exhausted stays exhausted.Can the same object be both the iterable and the iterator?
__iter__ returns self and __next__ exists on the same object. It's valid but single-use: it cannot be re-looped, since its one cursor is shared.Is a dictionary iterable, and what does iterating it yield?
__iter__ yields the keys (not key–value pairs), each iter(dict) giving a fresh key-iterator with its own position.A class with __getitem__ but no __iter__ — is it iterable?
iter() falls back to calling obj[0], obj[1], ... until IndexError, which it converts to StopIteration. This is how old-style sequences worked before __iter__ existed, and why some objects loop despite having no __iter__.Recall One-line summary of the traps
Iterable = "gives a dispenser," iterator = "is the dispenser." __iter__ on an iterator returns self; on a reusable iterable it returns a fresh one. StopIteration is a signal, not a failure, it never resets, and generators must return instead of raising it. Missing __iter__ but has __getitem__? The legacy obj[0], obj[1], ... fallback still makes it iterable.
Connections
- Iterators — `__iter__`, `__next__`, StopIteration (index 1.3.10) — the parent concept these traps drill.
- for loops and comprehensions — where the
try/except StopIterationmachinery hides. - Generators — yield, lazy evaluation — the auto-implemented alternative.
- itertools — islice, count, chain — safe handling of infinite iterators.
- Lazy evaluation and memory efficiency — why one-at-a-time matters.
- Dunder (magic) methods — the family
__iter__/__next__belong to. - StopIteration and PEP 479 — the generator
returnvsraiserule.