The Iterator Protocol: iter() and next()
20 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Every for loop in Python runs on one small rule: ask for an iterator, then ask it for the next value until it says it is done. Knowing this rule explains generators, zip, enumerate and file reading.
Iterable: anything a for loop can walk
Iterable is any object that can give you an iterator when you call iter() on it. Lists, strings, tuples, dictionaries, sets and files are iterable.
A list, a string, a dictionary and a file can all be looped over. Python calls such an object iterable. You get a fresh walker from it with iter().
marks = [72, 85, 91]print(type(marks).__name__)print(type(iter(marks)).__name__)print(type(iter("abc")).__name__)Example 1 · Iterables and their iterators
items = {"pen": 12, "book": 90}for source in ([1, 2], "ab", (3, 4), items): it = iter(source) print(type(source).__name__, "->", type(it).__name__)list -> list_iterator str -> str_ascii_iterator tuple -> tuple_iterator dict -> dict_keyiterator
What this code does
- Each type has its own iterator class, but they all work with
next(). - A dictionary walks over its keys.
The list is the data. The list_iterator is a separate object that walks it.
Iterator: a bookmark that moves with next()
Iterator is an object with a next step: calling next() on it returns the following value, and it raises StopIteration when the values run out.
An iterator remembers where it is. Each call to next() returns the next value and moves the bookmark forward. When nothing is left, it raises StopIteration.
it = iter([72, 85])print(next(it))print(next(it))try: next(it)except StopIteration: print("done")Example 2 · A default instead of StopIteration
queue = iter(["Asha", "Ravi"])print(next(queue, "empty"))print(next(queue, "empty"))print(next(queue, "empty"))Asha Ravi empty
What this code does
next(it, default)returns the default instead of raisingStopIteration.- This is handy when you only want the first match or a safe fallback.
The third call has no value to give, so the iterator signals the end with StopIteration.
What a for loop really does
Iterator protocol is the agreement behind every loop: an iterable has __iter__() returning an iterator, and the iterator has __next__() and raises StopIteration at the end.
A for loop calls iter() once, then calls next() again and again. It stops quietly when StopIteration appears. You can write the same loop by hand.
prices = (120, 80, 45)it = iter(prices)total = 0while True: try: total += next(it) except StopIteration: breakprint(total)Example 3 · A for loop written by hand
scores = [60, 75, 90]it = iter(scores)while True: try: score = next(it) except StopIteration: break print("score", score)score 60 score 75 score 90
What this code does
- This does exactly what
for score in scoresdoes. - The
breakonStopIterationis the quiet stop of the loop.
This while loop is what for price in prices: total += price does for you.
Writing your own iterator
To make a class an iterator, give it __iter__ that returns self and __next__ that returns the next value. Raise StopIteration when finished.
class Countdown: def __init__(self, start): self.current = start
def __iter__(self): return self
def __next__(self): if self.current < 1: raise StopIteration value = self.current self.current -= 1 return valueExample 4 · A Countdown iterator class
class Countdown: def __init__(self, start): self.current = start
def __iter__(self): return self
def __next__(self): if self.current < 1: raise StopIteration value = self.current self.current -= 1 return value
for n in Countdown(3): print(n)print("liftoff")3 2 1 liftoff
What this code does
- The loop calls
__next__until it raisesStopIteration. - The values are made when asked for, not stored in a list.
Because __iter__ returns self, a for loop can use a Countdown directly. The state lives in self.current.
Iterables you can loop many times
An iterator is used up after one pass. A reusable collection should be an iterable instead: its __iter__ returns a new iterator every time.
class OrderBook: def __init__(self, orders): self.orders = orders
def __iter__(self): return iter(self.orders)Example 5 · Iterator is used up, iterable is not
class OrderBook: def __init__(self, orders): self.orders = orders
def __iter__(self): return iter(self.orders)
book = OrderBook(["A1", "B2"])print(list(book), list(book))
walker = iter(["A1", "B2"])print(list(walker), list(walker))['A1', 'B2'] ['A1', 'B2'] ['A1', 'B2'] []
What this code does
bookgives a new iterator for eachlist()call, so both lists are full.walkeris one iterator. The firstlist()uses it up, so the second is empty.
Each for loop over an OrderBook calls __iter__ and gets a fresh walker, so the book can be read again and again.
Next step
Check your understanding
12 quick questions