When you write for item in my_object, Python needs to know how to get items from your object. The iterator protocol defines this contract - implement iter and next and your custom classes work seamlessly with for loops, list(), and all iteration tools.

The iterator protocol in Python defines how objects can be iterated over. Any object that implements __iter__() and __next__() is an iterator.

Basic Iterator

counter
basic.py
Replay: real traced execution (multi-file project)
# Basic iterator


class Counter:
    def __init__(self, start, end):
        self.current = start
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.end:
            raise StopIteration
        value = self.current
        self.current += 1
        return value


# Use the iterator
counter = Counter(1, 5)
for num in counter:
    print(num)

# Basic iterator


class Counter:
    def __init__(self, start, end):
        self.current = start
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.end:
            raise StopIteration
        value = self.current
        self.current += 1
        return value


# Use the iterator
counter = Counter(0, 3)
for num in counter:
    print(num)

# Basic iterator


class Counter:
    def __init__(self, start, end):
        self.current = start
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.end:
            raise StopIteration
        value = self.current
        self.current += 1
        return value


# Use the iterator
counter = Counter(2, 7)
for num in counter:
    print(num)

  1. counter = Counter(1, 5)

    20# Use the iterator21counter = Counter(1, 5)22#@counter=Counter(0, 3), Counter(2, 7)
  2. self.current ← 1, self.end ← 5

    4class Counter:5    def __init__(self⟨Counter A⟩, start1, end5):6        self.current→ 1 = start17        self.end→ 5 = end5
  3. counter ← ⟨Counter A⟩

    20# Use the iterator21counter→ ⟨Counter A⟩ = Counter(1, 5)22#@counter=Counter(0, 3), Counter(2, 7)
  4. def __iter__(self):

    9def __iter__(self⟨Counter A⟩):10    return self
  5. value ← 1, self.current ← 2

    pass 1 of 5
    12def __next__(self⟨Counter A⟩):13    if self.current >= self.end:14        raise StopIteration15    value→ 1 = self.current116    self.current→ 2 += 117    return value1
    All 5 passes — pass 1 is the card above
    passself.endStopIterationvalueself.current
    111 2
    222 3
    333 4
    444 5
    55<class 'StopIteration'>5
  6. for num in counter:

    pass 1 of 4
    22#@counter=Counter(0, 3), Counter(2, 7)23for num1 in counter⟨Counter A⟩:24    print(num1)
    output1
    All 4 passes — pass 1 is the card above
    passnumself.currentself.endStopIteration
    11
    22
    33
    4455<class 'StopIteration'>
  7. if self.current >= self.end:

    12def __next__(self):13    if self.current5 >= self.end5:14        raise StopIteration<class 'StopIteration'>15    value = self.current
  1. counter = Counter(0, 3)

    20# Use the iterator21counter = Counter(0, 3)22for num in counter:
  2. self.current ← 0, self.end ← 3

    4class Counter:5    def __init__(self⟨Counter A⟩, start0, end3):6        self.current→ 0 = start07        self.end→ 3 = end3
  3. counter ← ⟨Counter A⟩

    20# Use the iterator21counter→ ⟨Counter A⟩ = Counter(0, 3)22for num in counter:
  4. def __iter__(self):

    9def __iter__(self⟨Counter A⟩):10    return self
  5. value ← 0, self.current ← 1

    pass 1 of 4
    12def __next__(self⟨Counter A⟩):13    if self.current >= self.end:14        raise StopIteration15    value→ 0 = self.current016    self.current→ 1 += 117    return value0
    All 4 passes — pass 1 is the card above
    passself.endStopIterationvalueself.current
    100 1
    211 2
    322 3
    43<class 'StopIteration'>3
  6. for num in counter:

    pass 1 of 3
    21counter = Counter(0, 3)22for num0 in counter⟨Counter A⟩:23    print(num0)
    output0
    All 3 passes — pass 1 is the card above
    passnumself.currentself.endStopIteration
    10
    21
    3233<class 'StopIteration'>
  7. if self.current >= self.end:

    12def __next__(self):13    if self.current3 >= self.end3:14        raise StopIteration<class 'StopIteration'>15    value = self.current
  1. counter = Counter(2, 7)

    20# Use the iterator21counter = Counter(2, 7)22for num in counter:
  2. self.current ← 2, self.end ← 7

    4class Counter:5    def __init__(self⟨Counter A⟩, start2, end7):6        self.current→ 2 = start27        self.end→ 7 = end7
  3. counter ← ⟨Counter A⟩

    20# Use the iterator21counter→ ⟨Counter A⟩ = Counter(2, 7)22for num in counter:
  4. def __iter__(self):

    9def __iter__(self⟨Counter A⟩):10    return self
  5. value ← 2, self.current ← 3

    pass 1 of 6
    12def __next__(self⟨Counter A⟩):13    if self.current >= self.end:14        raise StopIteration15    value→ 2 = self.current216    self.current→ 3 += 117    return value2
    All 6 passes — pass 1 is the card above
    passself.endStopIterationvalueself.current
    122 3
    233 4
    344 5
    455 6
    566 7
    67<class 'StopIteration'>7
  6. for num in counter:

    pass 1 of 5
    21counter = Counter(2, 7)22for num2 in counter⟨Counter A⟩:23    print(num2)
    output2
    All 5 passes — pass 1 is the card above
    passnumself.currentself.endStopIteration
    12
    23
    34
    45
    5677<class 'StopIteration'>
  7. if self.current >= self.end:

    12def __next__(self):13    if self.current7 >= self.end7:14        raise StopIteration<class 'StopIteration'>15    value = self.current
iterator protocol - the __iter__() and __next__() methods that let objects work with for loops

How for Loops Work

manual.py
Replay: real traced execution (multi-file project)
# Using iter() and next()


data = [10, 20, 30]

# Get iterator manually
it = iter(data)  # calls data.__iter__()

# Get values manually
print(next(it))  # calls it.__next__() -> 10
print(next(it))  # -> 20
print(next(it))  # -> 30

# StopIteration on exhaustion
try:
    print(next(it))  # iterator exhausted
except StopIteration:
    print("Iterator exhausted")

  1. data ← [10, 20, 30], it ← ⟨list_iterator A⟩

    4data→ [10, 20, 30] = [10, 20, 30]56# Get iterator manually7it→ ⟨list_iterator A⟩ = iter(data[10, 20, 30])  # calls data.__iter__()89# Get values manually10print(next(it⟨list_iterator A⟩))  # calls it.__next__() -> 1011print(next(it⟨list_iterator A⟩))  # -> 2012print(next(it⟨list_iterator A⟩))  # -> 30
    output10
    20
    30
  2. try:

    14# StopIteration on exhaustion15try:16    print(next(it⟨list_iterator A⟩))  # iterator exhausted17except StopIteration:
  3. except StopIteration:

    16    print(next(it))  # iterator exhausted17except StopIteration:18    print("Iterator exhausted")
    outputIterator exhausted

The for loop:

for item in iterable:
    process(item)

Is equivalent to:

iterator = iter(iterable)  # calls __iter__()
while True:
    try:
        item = next(iterator)  # calls __next__()
        process(item)
    except StopIteration:
        break

Iterable vs Iterator

iterable_vs_iterator.py
Replay: real traced execution (multi-file project)
# Iterable vs iterator


class NumberList:
    """Iterable (not an iterator)"""

    def __init__(self, numbers):
        self.numbers = numbers

    def __iter__(self):
        return NumberIterator(self.numbers)


class NumberIterator:
    """Iterator for NumberList"""

    def __init__(self, numbers):
        self.numbers = numbers
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.numbers):
            raise StopIteration
        value = self.numbers[self.index]
        self.index += 1
        return value


# Use the iterable multiple times
numbers = NumberList([10, 20, 30])

print("First iteration:")
for n in numbers:
    print(n)

print("\nSecond iteration:")
for n in numbers:
    print(n)

  1. """Iterable (not an iterator)"""

    4class NumberList:5    """Iterable (not an iterator)"""67    def __init__(self, numbers):8        self.numbers = numbers910    def __iter__(self):11        return NumberIterator(self.numbers)121314class NumberIterator:15    """Iterator for NumberList"""1617    def __init__(self, numbers):18        self.numbers = numbers19        self.index = 02021    def __iter__(self):22        return self2324    def __next__(self):25        if self.index >= len(self.numbers):26            raise StopIteration27        value = self.numbers[self.index]28        self.index += 129        return value303132# Use the iterable multiple times33numbers = NumberList([10, 20, 30])
  2. self.numbers ← [10, 20, 30]

    7def __init__(self⟨NumberList A⟩, numbers[10, 20, 30]):8    self.numbers→ [10, 20, 30] = numbers[10, 20, 30]
  3. numbers ← ⟨NumberList A⟩

    32# Use the iterable multiple times33numbers→ ⟨NumberList A⟩ = NumberList([10, 20, 30])3435print("First iteration:")36for n in numbers:
    outputFirst iteration:
  4. def __iter__(self):

    pass 1 of 2
    10def __iter__(self⟨NumberList A⟩):11    return NumberIterator(self.numbers[10, 20, 30])
  5. self.numbers ← [10, 20, 30], self.index ← 0

    pass 1 of 2
    17def __init__(self⟨NumberIterator B⟩, numbers[10, 20, 30]):18    self.numbers→ [10, 20, 30] = numbers[10, 20, 30]19    self.index→ 0 = 0
  6. value ← 10, self.index ← 1

    pass 1 of 8
    24def __next__(self⟨NumberIterator B⟩):25    if self.index >= len(self.numbers):26        raise StopIteration27    value→ 10 = self.numbers[self.index]1028    self.index→ 1 += 129    return value10
    All 8 passes — pass 1 is the card above
    passselfself.numbers[self.index]self.numbersStopIterationvalueself.index
    1⟨NumberIterator B⟩10100 1
    2⟨NumberIterator B⟩20201 2
    3⟨NumberIterator B⟩30302 3
    4⟨NumberIterator B⟩[10, 20, 30]<class 'StopIteration'>3
    5⟨NumberIterator C⟩10100 1
    6⟨NumberIterator C⟩20201 2
    7⟨NumberIterator C⟩30302 3
    8⟨NumberIterator C⟩[10, 20, 30]<class 'StopIteration'>3
  7. for n in numbers:

    pass 1 of 3
    35print("First iteration:")36for n10 in numbers⟨NumberList A⟩:37    print(n10)
    output10
    All 3 passes — pass 1 is the card above
    passnself.indexself.numbersStopIteration
    110
    220
    3303[10, 20, 30]<class 'StopIteration'>
  8. if self.index >= len(self.numbers):

    pass 1 of 2
    24def __next__(self):25    if self.index3 >= len(self.numbers[10, 20, 30]):26        raise StopIteration<class 'StopIteration'>27    value = self.numbers[self.index]
  9. print(" Second iteration:")

    39print("\nSecond iteration:")40for n in numbers:
    output
    Second iteration:
  10. def __iter__(self):

    pass 2 of 2
    10def __iter__(self⟨NumberList A⟩):11    return NumberIterator(self.numbers[10, 20, 30])
  11. self.numbers ← [10, 20, 30], self.index ← 0

    pass 2 of 2
    17def __init__(self⟨NumberIterator C⟩, numbers[10, 20, 30]):18    self.numbers→ [10, 20, 30] = numbers[10, 20, 30]19    self.index→ 0 = 0
  12. for n in numbers:

    pass 1 of 3
    39print("\nSecond iteration:")40for n10 in numbers⟨NumberList A⟩:41    print(n10)
    output10
    All 3 passes — pass 1 is the card above
    passnself.indexself.numbersStopIteration
    110
    220
    3303[10, 20, 30]<class 'StopIteration'>
  13. if self.index >= len(self.numbers):

    pass 2 of 2
    24def __next__(self):25    if self.index3 >= len(self.numbers[10, 20, 30]):26        raise StopIteration<class 'StopIteration'>27    value = self.numbers[self.index]
iterable - an object with __iter__() that returns an iterator; can be iterated multiple times
StopIteration - the exception that signals the end of iteration

Custom Range Implementation

custom_range.py
Replay: real traced execution (multi-file project)
# Custom range-like iterator


class MyRange:
    def __init__(self, start, stop, step=1):
        self.current = start
        self.stop = stop
        self.step = step

    def __iter__(self):
        return self

    def __next__(self):
        if (self.step > 0 and self.current >= self.stop) or (
            self.step < 0 and self.current <= self.stop
        ):
            raise StopIteration
        value = self.current
        self.current += self.step
        return value


# Use like built-in range
print("Forward:")
for i in MyRange(0, 10, 2):
    print(i, end=" ")

print("\n\nBackward:")
for i in MyRange(10, 0, -2):
    print(i, end=" ")

  1. print("Forward:")

    23# Use like built-in range24print("Forward:")25for i in MyRange(0, 10, 2):
    outputForward:
  2. self.current ← 0, self.stop ← 10, self.step ← 2

    pass 1 of 2
    4class MyRange:5    def __init__(self⟨MyRange A⟩, start0, stop10, step2=1):6        self.current→ 0 = start07        self.stop→ 10 = stop108        self.step→ 2 = step2
  3. def __iter__(self):

    pass 1 of 2
    10def __iter__(self⟨MyRange A⟩):11    return self
  4. value ← 0, self.current ← 2

    pass 1 of 12
    13def __next__(self⟨MyRange A⟩):14    if (self.step > 0 and self.current >= self.stop) or (15        self.step < 0 and self.current <= self.stop16    ):17        raise StopIteration18    value→ 0 = self.current019    self.current→ 2 += self.step220    return value0
    All 12 passes — pass 1 is the card above
    passselfself.stepself.stopStopIterationvalueself.current
    1⟨MyRange A⟩200 2
    2⟨MyRange A⟩222 4
    3⟨MyRange A⟩244 6
    4⟨MyRange A⟩266 8
    5⟨MyRange A⟩288 10
    6⟨MyRange A⟩210<class 'StopIteration'>10
    7⟨MyRange B⟩-21010 8
    8⟨MyRange B⟩-288 6
    9⟨MyRange B⟩-266 4
    10⟨MyRange B⟩-244 2
    11⟨MyRange B⟩-222 0
    12⟨MyRange B⟩-20<class 'StopIteration'>0
  5. for i in MyRange(0, 10, 2):

    pass 1 of 5
    24print("Forward:")25for i0 in MyRange(0, 10, 2):26    print(i0, end=" ")
    output0
    All 5 passes — pass 1 is the card above
    passiself.stepself.currentself.stopStopIteration
    10
    22
    34
    46
    5821010<class 'StopIteration'>
  6. if (self.step > 0 and self.current >= self.stop) or ( self…

    pass 1 of 2
    13def __next__(self):14    if (self.step2 > 0 and self.current10 >= self.stop10) or (15        self.step2 < 0 and self.current10 <= self.stop1016    ):17        raise StopIteration<class 'StopIteration'>18    value = self.current
  7. print(" Backward:")

    28print("\n\nBackward:")29for i in MyRange(10, 0, -2):
    output
    
    Backward:
  8. self.current ← 10, self.stop ← 0, self.step ← -2

    pass 2 of 2
    4class MyRange:5    def __init__(self⟨MyRange B⟩, start10, stop0, step-2=1):6        self.current→ 10 = start107        self.stop→ 0 = stop08        self.step→ -2 = step-2
  9. def __iter__(self):

    pass 2 of 2
    10def __iter__(self⟨MyRange B⟩):11    return self
  10. for i in MyRange(10, 0, -2):

    pass 1 of 5
    28print("\n\nBackward:")29for i10 in MyRange(10, 0, -2):30    print(i10, end=" ")
    output10
    All 5 passes — pass 1 is the card above
    passiself.stepself.currentself.stopStopIteration
    110
    28
    36
    44
    52-200<class 'StopIteration'>
  11. if (self.step > 0 and self.current >= self.stop) or ( self…

    pass 2 of 2
    13def __next__(self):14    if (self.step-2 > 0 and self.current0 >= self.stop0) or (15        self.step-2 < 0 and self.current0 <= self.stop016    ):17        raise StopIteration<class 'StopIteration'>18    value = self.current

Infinite Iterators

infinite.py
Replay: real traced execution (multi-file project)
# Infinite iterator


class InfiniteCounter:
    def __init__(self, start=0):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        value = self.current
        self.current += 1
        return value


# Infinite iterator (use break to stop)
counter = InfiniteCounter(1)
for num in counter:
    print(num)
    if num >= 5:
        break

  1. counter = InfiniteCounter(1)

    17# Infinite iterator (use break to stop)18counter = InfiniteCounter(1)19for num in counter:
  2. self.current ← 1

    4class InfiniteCounter:5    def __init__(self⟨InfiniteCounter A⟩, start1=0):6        self.current→ 1 = start1
  3. counter ← ⟨InfiniteCounter A⟩

    17# Infinite iterator (use break to stop)18counter→ ⟨InfiniteCounter A⟩ = InfiniteCounter(1)19for num in counter:
  4. def __iter__(self):

    8def __iter__(self⟨InfiniteCounter A⟩):9    return self
  5. value ← 1, self.current ← 2

    pass 1 of 5
    11def __next__(self⟨InfiniteCounter A⟩):12    value→ 1 = self.current113    self.current→ 2 += 114    return value1
    All 5 passes — pass 1 is the card above
    passnumvalueself.current
    111 2
    222 3
    333 4
    444 5
    5555 6
  6. for num in counter:

    pass 1 of 5
    18counter = InfiniteCounter(1)19for num1 in counter⟨InfiniteCounter A⟩:20    print(num1)21    if num >= 5:
    output1
    All 5 passes — pass 1 is the card above
    passnum
    11
    22
    33
    44
    55
  7. if num >= 5:

    20print(num)21if num5 >= 5:22    break

Why It Matters

Understanding the iterator protocol helps you:

  • Create custom iterables
  • Understand how for loops work
  • Debug iteration issues
  • Implement lazy evaluation

Exercise: practical.py

Create a circular buffer iterator that loops through elements