Codectionary / Developer documentation / Python

Iterator protocol

Understand how iter and next drive loops, and implement a finite custom iterator.

Understand the contract

An iterable can provide an iterator through iter(). An iterator holds traversal state and produces one value at a time through next(). Lists are reusable iterables: asking a list for a new iterator starts an independent traversal.

An iterator implements __iter__ returning itself and __next__ returning the next item. Once no items remain, __next__ raises StopIteration. A for loop uses this protocol and handles normal exhaustion for you.

Follow the runtime behavior

Advancing an iterator changes its state. Consuming it with list or a loop leaves it exhausted; an exhausted iterator should continue raising StopIteration. next(iterator, default) returns the default at exhaustion instead of raising.

One-pass iterators are useful for streams, but sharing one between consumers means they share progress. To repeat a traversal, keep the original reusable collection or create a fresh iterator from a factory.

Apply it and practise

Use a custom iterator when an object needs explicit traversal state; use a generator function when yield expresses the same sequence more simply. Avoid building a whole list when values can be processed incrementally.

Exercise: implement an iterator yielding 0, 2, 4 and then stopping. Verify iter(iterator) is iterator, consume two values with next, then collect the remainder. Confirm that a second list call is empty.

Syntax

iterator = iter(values)
item = next(iterator)

Examples

Advance and exhaust

Use next with a default when reading a possibly empty stream one item at a time.

iterator = iter([10, 20])
print(next(iterator))
print(next(iterator))
print(next(iterator, "done"))
print(list(iterator))
  1. Each next consumes one element. The third call uses its default because the iterator is exhausted, and list finds no remaining values.
  2. Use next with a default when reading a possibly empty stream one item at a time.
  3. Choose a default distinguishable from legitimate elements if callers must detect exhaustion unambiguously.
  4. Expected output: 10 20 done []

Create independent traversals

Use independent iterators when two consumers need to traverse the same stable collection.

values = ["a", "b"]
first = iter(values)
second = iter(values)
print(next(first))
print(next(first))
print(next(second))
print(first is iter(first))
  1. The list creates two different iterators with independent positions. Asking first for its own iterator returns first itself, which is the iterator protocol rule.
  2. Use independent iterators when two consumers need to traverse the same stable collection.
  3. Mutating the collection while traversing can complicate the result; this example deliberately leaves it unchanged.
  4. Expected output: a b a True

Implement a finite iterator

Use a stateful iterator to expose a finite sequence without storing every value.

class Countdown:
    def __init__(self, start):
        self.remaining = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.remaining <= 0:
            raise StopIteration
        value = self.remaining
        self.remaining -= 1
        return value

counter = Countdown(3)
print(list(counter))
print(list(counter))
  1. __next__ saves the current count, decrements state, and returns the saved value. At zero it raises StopIteration, so both subsequent traversals remain exhausted.
  2. Use a stateful iterator to expose a finite sequence without storing every value.
  3. Countdown is its own iterator and is therefore not restartable; create another instance to repeat the sequence.
  4. Expected output: [3, 2, 1] []

Best practices

  • One-pass iterators are useful for streams, but sharing one between consumers means they share progress. To repeat a traversal, keep the original reusable collection or create a fresh iterator from a factory.
  • Use a custom iterator when an object needs explicit traversal state; use a generator function when yield expresses the same sequence more simply. Avoid building a whole list when values can be processed incrementally.

Runtime & versions

Python interpreter. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

Python language reference

Specifications & further reading