Advertisement
🚀 Advanced Python

Python Iterators – __iter__, __next__, and the Iterator Protocol

Iterators are the engine behind Python's for loops, list comprehensions, and unpacking. Everything you iterate over — lists, strings, dicts, files, generators — is an iterator or iterable under the hood. Understanding the iterator protocol lets you create custom sequences and work efficiently with large data.

⏱️ 20 min read🎯 Advanced📅 Updated 2026

The Iterator Protocol

An object is an iterator if it implements two methods: __iter__() (returns the iterator itself) and __next__() (returns the next value, raises StopIteration when done).

Python
# What a for loop actually does:
my_list = [1, 2, 3]
iterator = iter(my_list)   # calls __iter__()

print(next(iterator))   # 1 — calls __next__()
print(next(iterator))   # 2
print(next(iterator))   # 3
# next(iterator)        # Raises StopIteration
▶ Output
1 2 3

Creating a Custom Iterator

Implement __iter__ and __next__ to make any class iterable.

Python
class Countdown:
    """Counts from n down to 1."""
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self   # The iterator IS the object

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

for num in Countdown(5):
    print(num, end=" ")
▶ Output
5 4 3 2 1

Iterators vs Generators

Generators are iterators created with yield — much less code for the same result.

Python
# Iterator (verbose)
class EvenNumbers:
    def __init__(self, limit):
        self.num, self.limit = 0, limit
    def __iter__(self): return self
    def __next__(self):
        if self.num > self.limit: raise StopIteration
        val = self.num; self.num += 2; return val

# Generator (same thing, 4 lines)
def even_numbers(limit):
    for n in range(0, limit+1, 2):
        yield n

print(list(even_numbers(10)))
▶ Output
[0, 2, 4, 6, 8, 10]
Advertisement

itertools – Powerful Iterator Combinators

The itertools module provides fast, memory-efficient iterator tools.

Python
import itertools

# chain: combine multiple iterables
result = list(itertools.chain([1,2], [3,4], [5]))
print(result)   # [1, 2, 3, 4, 5]

# islice: slice any iterator
result = list(itertools.islice(range(100), 5))
print(result)   # [0, 1, 2, 3, 4]

# cycle: repeat infinitely
counter = 0
for item in itertools.cycle(["A","B","C"]):
    print(item, end=" ")
    counter += 1
    if counter == 7: break
▶ Output
[1, 2, 3, 4, 5] [0, 1, 2, 3, 4] A B C A B C A

Iterable vs Iterator: What a for Loop Actually Does

These two words get mixed up constantly. An iterable is anything you can loop over (list, string, dict). An iterator is the object that does the actual stepping, remembering its position. A for loop quietly turns the first into the second.

# What `for x in [1,2,3]:` really runs under the hood:
it = iter([1, 2, 3])     # __iter__ → an iterator
while True:
    try:
        x = next(it)     # __next__ → next value
    except StopIteration:
        break            # iterator signals "done" by raising this
IterableIterator
Has__iter____iter__ + __next__
Example[1,2,3], "abc"result of iter(...)
Positionnoneremembers where it is

Key consequence: an iterator is one-shot. Once exhausted it stays empty — loop it again and you get nothing. A list is reusable because each for asks it for a fresh iterator. Build custom iterators by defining both dunder methods, or just use a generator (which is an iterator for free).

🏋️ Practical Exercise

Understand the iterator protocol:

  1. Call iter() on a list and step through it manually with next() until StopIteration.
  2. Write a custom iterator class implementing __iter__ and __next__ that counts down from n.
  3. Use it in a for loop.
  4. Reproduce the same behavior with itertools.count and itertools.islice.

🔥 Challenge Exercise

Build a custom iterator class Fibonacci(limit) that yields Fibonacci numbers until they exceed a limit, implementing __iter__ and __next__ and raising StopIteration correctly. Then rebuild the same thing as a generator function and compare the code length. Finally, use itertools.takewhile and itertools.count to produce the same sequence in one expression.

📋 Summary

  • An iterable can return an iterator via __iter__; an iterator produces values via __next__.
  • A for loop calls iter() then repeatedly calls next() until StopIteration.
  • A custom iterator implements both __iter__ (returning self) and __next__.
  • Generators are a concise way to create iterators using yield.
  • Iterators are lazy and memory-efficient — they produce values one at a time.
  • itertools offers ready-made iterator building blocks like count, islice, and takewhile.

Interview Questions on Iterators

  • What is the difference between an iterable and an iterator?
  • What two methods make up the iterator protocol?
  • What does StopIteration signal?
  • What is the difference between an iterator and a generator?
  • How does a for loop use iterators internally?
  • What is the itertools module used for?
  • Why are iterators memory-efficient?

FAQ

What is the difference between an iterable and an iterator? +

An iterable is anything you can loop over (list, string, dict) — it implements __iter__. An iterator is the object that actually produces values one at a time via __next__ and remembers its position. Calling iter() on an iterable gives you an iterator.

What is the difference between an iterator and a generator? +

A generator is a simple way to build an iterator using a function with yield; Python writes __iter__ and __next__ for you. A hand-written iterator class gives more control but requires more code.

What does StopIteration mean? +

It is the exception an iterator raises from __next__ when there are no more items. A for loop catches it automatically to know when to stop.

Why are iterators memory-efficient? +

They compute and hand out one value at a time instead of building the whole sequence in memory. This makes them ideal for large or infinite streams of data.