Python Iterators

Ka Kavitha V Updated 03 Oct 2026
6 min read

Iterators

Every time you write a for loop in Python, an iterator is working behind the scenes. Understanding iterators explains how for loops actually work, why some objects can only be looped over once, and how to build your own objects that can be used in a for loop.

What Is an Iterator?

An iterator is an object that lets you move through a sequence of values one element at a time. It remembers its current position, and each time you ask for the next value, it returns that value and advances.

Iterators can produce values on demand instead of holding all of them in memory at once, which makes them efficient for large or even endless sequences.

The Iterator Protocol

In Python, an object is an iterator if it follows the iterator protocol — that is, if it implements two special methods:

MethodPurpose
__iter__()Returns the iterator object itself
__next__()Returns the next value; raises StopIteration when no values remain

The built-in functions iter() and next() call these methods for you.

Iterable vs Iterator

These two terms sound similar but mean different things:

TermMeaningExamples
IterableAn object you can loop over. It can produce an iterator.Lists, tuples, strings, sets, dictionaries, ranges
IteratorThe object that does the looping — it tracks the position and returns values one by one.The object returned by iter(my_list), file objects, generators

An analogy: a book is iterable — it can be read. A bookmark is the iterator — it remembers which page you are on and moves forward as you read. You can have several bookmarks in the same book, each at a different page.

Getting an Iterator From an Iterable

Use the built-in iter() function to get an iterator from any iterable, and next() to retrieve values from it.

Example: Iterator From a List

fruits = ["apple", "orange", "mango"]
iterator = iter(fruits)

print(next(iterator))
print(next(iterator))
print(next(iterator))

Expected output:

apple
orange
mango

Each call to next() returns the next item and moves the iterator forward.

What Happens When the Values Run Out?

Calling next() again after the last item raises StopIteration:

fruits = ["apple", "orange", "mango"]
iterator = iter(fruits)

next(iterator)
next(iterator)
next(iterator)
next(iterator)

Output:

StopIteration

StopIteration is not really an error in normal use — it is the signal that the iterator is finished.

Example: Iterator From a String

Strings are iterable, character by character.

text = "python"
char_iter = iter(text)

print(next(char_iter))
print(next(char_iter))
print(next(char_iter))

Expected output:

p
y
t

The iterator still remembers its position: the next call to next(char_iter) would return h.

Iterators Are Used Up

An iterator moves only forward. Once it has produced all its values, it is exhausted:

numbers = [1, 2, 3]
it = iter(numbers)

print(list(it))
print(list(it))

Expected output:

[1, 2, 3]
[]

The second list(it) is empty because the iterator has already been consumed. The original list, however, is unchanged — calling iter(numbers) again creates a fresh iterator.

Looping Through an Iterable

In practice, you rarely call next() yourself. A for loop does it automatically.

Example: Looping Through a Set

colors = {"red", "blue", "green"}

for color in colors:
    print(color)

Example output (set order may vary):

blue
green
red

Example: Looping Through a String

word = "code"

for letter in word:
    print(letter)

Expected output:

c
o
d
e

How a for Loop Works Internally

Behind the scenes, a for loop:

  1. Calls iter() on the iterable to get an iterator.
  2. Calls next() on the iterator repeatedly, assigning each value to the loop variable.
  3. Stops quietly when StopIteration is raised.

This for loop:

for letter in "code":
    print(letter)

behaves like this while loop:

iterator = iter("code")

while True:
    try:
        letter = next(iterator)
    except StopIteration:
        break
    print(letter)

Both print the same four letters. The for loop is simply a convenient way of writing the iterator protocol.

Creating Your Own Iterator

To make a custom iterator, define a class with the two protocol methods:

  • __iter__() — prepares the iterator and returns self
  • __next__() — returns the next value

Note: This section uses classes, which are covered in detail in the Python Classes and Objects lesson. self refers to the object itself and is how the iterator stores its current state between calls.

Example: Custom Iterator for Even Numbers

This iterator produces even numbers starting from 2.

class EvenNumbers:
    def __iter__(self):
        self.num = 2
        return self

    def __next__(self):
        value = self.num
        self.num += 2
        return value

even_iter = iter(EvenNumbers())

print(next(even_iter))
print(next(even_iter))
print(next(even_iter))
print(next(even_iter))

Expected output:

2
4
6
8

How it works:

  1. iter(EvenNumbers()) calls __iter__(), which sets the starting value self.num = 2 and returns the object.
  2. Each next() call runs __next__(), which saves the current number, increases self.num by 2 for next time, and returns the saved number.

This iterator never runs out. Using it directly in a for loop would create an infinite loop.

Stopping Iteration With StopIteration

To make an iterator finite, raise StopIteration inside __next__() when there are no more values.

Example: Iterator With a Stop Condition

This iterator produces the squares of 1 through 5.

class SquareLimit:
    def __iter__(self):
        self.n = 1
        return self

    def __next__(self):
        if self.n <= 5:
            result = self.n ** 2
            self.n += 1
            return result
        else:
            raise StopIteration

squares = SquareLimit()

for value in squares:
    print(value)

Expected output:

1
4
9
16
25

What happens:

  1. The for loop calls iter(squares), which sets self.n = 1.
  2. Each loop iteration calls __next__(), which returns the square of n and increases n.
  3. When n becomes 6, __next__() raises StopIteration, and the for loop ends cleanly.

Making the Limit Configurable

Using __init__() (the method that runs when an object is created), you can pass the limit in:

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

    def __iter__(self):
        return self

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

for number in Countdown(3):
    print(number)

Expected output:

3
2
1

Iterators vs Generators

Writing an iterator class requires __iter__(), __next__(), and manual state tracking. A generator function achieves the same result with much less code, because Python creates the iterator automatically:

def countdown(start):
    while start > 0:
        yield start
        start -= 1

for number in countdown(3):
    print(number)

Expected output:

3
2
1

Every generator is an iterator. Use a generator for most custom iteration needs, and an iterator class when you need extra methods or more complex state. Generators are covered in the Python Functions lesson.

Common Mistakes

MistakeProblem
Calling next() on a list directlyTypeError: 'list' object is not an iterator — use iter() first
Looping over an exhausted iteratorProduces nothing
Forgetting to raise StopIterationInfinite iteration
Forgetting return self in __iter__()TypeError: iter() returned non-iterator
  • Python for Loops — the main consumer of iterators
  • Python Functions — generators and the yield keyword
  • Python Classes and Objects — building custom iterator classes
  • Python Range — an iterable that produces numbers on demand

0 Comments

Reviewed before they appear

No comments yet.

Python
Ask about this post
AI Ask about this post

Ask questions about Python Iterators and get answers drawn from it.

Signed-in readers only.