All Courses
Advanced Python

Python Iterators

Key Definitions

Term Definition
Iterable Any object that can be looped through (list, tuple, set, dict, string, map, filter, etc.)
Iterator A special object used to retrieve values from an iterable one at a time
iter(obj) Returns the iterator for an iterable object
next(iterator) Returns the next value from the iterator
StopIteration Exception raised when the iterator has no more values

What Actually Happens in a for Loop

Every for loop secretly uses an iterator under the hood:

x = [1, 2, 3]

# What you write:
for val in x:
    print(val)

# What Python actually does:
x_iter = iter(x)          # calls x.__iter__()
while True:
    try:
        val = next(x_iter)    # calls x_iter.__next__()
        print(val)
    except StopIteration:
        break

iter() and next() Functions

x = [1, 2, 3]
x_iter = iter(x)

print(next(x_iter))   # 1
print(next(x_iter))   # 2
print(next(x_iter))   # 3
print(next(x_iter))   # ❌ StopIteration
  • iter(x) → returns a list_iterator object (not the list itself)
  • next() must be called on the iterator, not the original object
next(x)        # ❌ TypeError: list object is not an iterator
next(iter(x))  # ✅ 1

Dunder Methods Behind the Scenes

Function Dunder method called
iter(obj) obj.__iter__()
next(iterator) iterator.__next__()
x_iter = x.__iter__()      # same as iter(x)
x_iter.__next__()          # same as next(x_iter)

What Makes an Object Iterable?

  • Has a __iter__ method → it is iterable
  • Has a __next__ method → it is an iterator

Creating a Custom Iterator — Two Classes

The clean approach: one class for the iterable, a separate class for the iterator.

class Numbers:
    def __init__(self, num1, num2, num3):
        self.num1 = num1
        self.num2 = num2
        self.num3 = num3

    def __iter__(self):
        return NumberIterator(self.num1, self.num2, self.num3)


class NumberIterator:
    def __init__(self, one, two, three):
        self.one = one
        self.two = two
        self.three = three
        self.current = 0

    def __next__(self):
        self.current += 1
        if self.current == 1:
            return self.one
        elif self.current == 2:
            return self.two
        elif self.current == 3:
            return self.three
        else:
            raise StopIteration
nums = Numbers(1, 2, 3)

for num in nums:
    print(num)   # 1  2  3

Creating a Custom Iterator — One Class

You can combine the iterable and iterator into a single class by defining both __iter__ and __next__:

class Numbers:
    def __init__(self, num1, num2, num3):
        self.num1 = num1
        self.num2 = num2
        self.num3 = num3
        self.current = 0

    def __iter__(self):
        self.current = 0   # reset on each new iteration
        return self        # return the object itself as the iterator

    def __next__(self):
        self.current += 1
        if self.current == 1:
            return self.num1
        elif self.current == 2:
            return self.num2
        elif self.current == 3:
            return self.num3
        else:
            raise StopIteration
nums = Numbers(1, 2, 3)
for num in nums:
    print(num)   # 1  2  3

⚠️ One-Class Approach Limitation — Shared State

When __iter__ returns self, all iterators from the same object share the same internal state:

nums = Numbers(1, 2, 3)

iter1 = iter(nums)
iter2 = iter(nums)   # resets current to 0 on the SAME object

print(next(iter1))   # 1
# creating iter2 reset current, so:
print(next(iter1))   # 1 again — not 2!

Why: iter1 and iter2 are literally the same object (nums). Creating iter2 reset current = 0, which affects iter1.

Two-class approach avoids this ✅

iter1 = iter(nums)   # returns a NEW NumberIterator instance
iter2 = iter(nums)   # returns a different NEW NumberIterator instance
# They have completely independent internal state

Summary — Two Approaches

Two-class approach One-class approach
__iter__ returns A new iterator object self
Independent iterators ✅ Yes ❌ No — shared state
Complexity Slightly more code Simpler
Best practice ✅ Preferred ⚠️ Use with caution

Key Takeaways & Recap

Concept Summary
Iterator Object with __next__ — returns the next value or raises StopIteration
Iterable Object with __iter__ — returns an iterator
for loop Calls iter() then next() repeatedly until StopIteration
StopIteration Signals the iterator is exhausted — for loop catches this and stops
Two-class design Preferred — iterable and iterator have separate, independent instances
One-class design Simpler but all calls to iter() return the same object — shared state