Python Iterators
Learn how Python processes sequences one item at a time,
how for loops actually work internally, and
how to build your own custom iterators using
__iter__() and __next__().
Learning Objectives
By the end of this lesson, you will understand how Python's iteration system works and how objects can provide values one at a time.
- Understand iteration and iterables.
- Understand the difference between an iterable and an iterator.
-
Use
iter()andnext(). -
Understand how
forloops work internally. -
Understand
StopIteration. - Build custom iterators.
-
Use
__iter__()and__next__(). - Understand iterator state.
- Apply iterators to real-world problems.
What Is Iteration?
Iteration means going through a collection or sequence one item at a time.
You have already used iteration many times with
for loops.
numbers = [10, 20, 30, 40]
for number in numbers:
print(number)
10
20
30
40
The for loop retrieves each value from the
collection until there are no more values.
for loops possible.
Iterable vs Iterator
These two terms sound similar, but they are not exactly the same.
Iterable
An iterable is an object that can provide an iterator.
Common iterables include:
- Lists
- Tuples
- Strings
- Sets
- Dictionaries
- Files
- Ranges
numbers = [1, 2, 3]
name = "Python"
values = {10, 20, 30}
for item in numbers:
print(item)
Iterator
An iterator is an object that produces values one at a time and remembers its current position.
An iterable can give you an iterator. An iterator actually produces the next values.
The iter() Function
The built-in iter() function creates or
retrieves an iterator from an iterable.
numbers = [10, 20, 30]
iterator = iter(numbers)
print(iterator)
Now iterator is an iterator over the list.
We can ask it for individual values using
next().
The next() Function
The next() function retrieves the next
available value from an iterator.
numbers = [10, 20, 30]
iterator = iter(numbers)
print(next(iterator))
print(next(iterator))
print(next(iterator))
10
20
30
Every time next() is called, the iterator
moves forward.
next() after the iterator has
no more values causes StopIteration.
How for Loops Work
A for loop hides much of the iterator
machinery from you.
Conceptually, this:
numbers = [10, 20, 30]
for number in numbers:
print(number)
behaves roughly like:
numbers = [10, 20, 30]
iterator = iter(numbers)
while True:
try:
number = next(iterator)
print(number)
except StopIteration:
break
for instead of writing
this machinery yourself. Understanding it helps you
understand how Python works internally.
StopIteration
When an iterator has no more values, it signals that
iteration is finished by raising
StopIteration.
numbers = [1, 2]
iterator = iter(numbers)
print(next(iterator))
print(next(iterator))
print(next(iterator))
1
2
StopIteration
A normal for loop handles this automatically.
You generally do not see the exception when using
standard iteration.
Creating a Custom Iterator
One of the most important skills in this lesson is creating your own iterator.
A typical custom iterator implements:
__iter__()__next__()
These methods define the iterator protocol.
__iter__()
The __iter__() method returns an iterator.
For an iterator object, __iter__() normally
returns self.
class Counter:
def __iter__(self):
return self
Returning self means the object itself is
the iterator.
__next__()
The __next__() method returns the next value.
When there are no more values, it must raise
StopIteration.
class Counter:
def __init__(self, maximum):
self.current = 1
self.maximum = maximum
def __iter__(self):
return self
def __next__(self):
if self.current <= self.maximum:
value = self.current
self.current += 1
return value
raise StopIteration
The current variable stores the iterator's
current position.
Complete Iterator Class
Let's combine everything into a working iterator.
class Counter:
def __init__(self, maximum):
self.current = 1
self.maximum = maximum
def __iter__(self):
return self
def __next__(self):
if self.current <= self.maximum:
value = self.current
self.current += 1
return value
raise StopIteration
counter = Counter(5)
for number in counter:
print(number)
1
2
3
4
5
The for loop automatically calls
iter() and repeatedly calls
next() until StopIteration
occurs.
Iterator State
An important characteristic of an iterator is that it remembers where it currently is.
numbers = [10, 20, 30]
iterator = iter(numbers)
print(next(iterator))
print(next(iterator))
print(next(iterator))
The iterator does not start from the beginning every time. It remembers its current position.
Every call to next() moves the
bookmark forward.
Iterators Are Usually One-Time Use
Once an iterator reaches the end, it normally stays exhausted.
numbers = [1, 2, 3]
iterator = iter(numbers)
for number in iterator:
print(number)
print("Again:")
for number in iterator:
print(number)
1
2
3
Again:
The second loop has nothing to process because the iterator has already been exhausted.
Converting Iterables into Iterators
Many Python objects are iterable but are not themselves iterators.
numbers = [10, 20, 30]
iterator = iter(numbers)
print(next(iterator))
print(next(iterator))
The list is the iterable, while the object returned by
iter(numbers) is the iterator.
Checking with iter()
An object can be considered iterable when Python can obtain an iterator from it.
values = [1, 2, 3]
iterator = iter(values)
print(next(iterator))
Practical Examples
Example 1 — Countdown
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(5):
print(number)
5
4
3
2
1
Example 2 — Even Numbers
class EvenNumbers:
def __init__(self, maximum):
self.current = 0
self.maximum = maximum
def __iter__(self):
return self
def __next__(self):
self.current += 2
if self.current <= self.maximum:
return self.current
raise StopIteration
for number in EvenNumbers(10):
print(number)
2
4
6
8
10
Example 3 — Processing Records
Iterators can be useful when processing records one at a time instead of manually handling the entire sequence.
records = [
"Order 1001",
"Order 1002",
"Order 1003"
]
orders = iter(records)
print(next(orders))
print(next(orders))
This concept becomes especially useful when working with large datasets, files, streams, and other potentially large sources of data.
Common Mistakes
1. Forgetting to raise StopIteration
A custom iterator must signal when there are no more values.
2. Forgetting to update the state
If the current position never changes,
__next__() may repeatedly return
the same value.
3. Confusing iterable with iterator
A list is iterable, but it is not the same thing
as the iterator returned by iter().
4. Reusing an exhausted iterator
Once an iterator is exhausted, create a new iterator if you need to start again.
5. Writing unnecessary custom iterators
Python already provides many useful iterable types. Build a custom iterator when you have a genuine reason to control iteration behavior.
Practice Exercises
Exercise 1 — Manual Iteration
Create a list containing five numbers. Convert it
into an iterator and retrieve each value using
next().
Exercise 2 — Countdown
Create a custom iterator that counts backward from a given number to 1.
Exercise 3 — Odd Numbers
Create an iterator that produces odd numbers up to a specified maximum.
Exercise 4 — Custom Range
Build a custom iterator that behaves similarly to a simple numeric range.
Exercise 5 — Product IDs
Create an iterator that produces product IDs from 1001 through 1010.
Exercise 6 — Iterator State
Add a property that lets you inspect the current position of your custom iterator.
Mini Challenge — Custom Number Iterator
Build a reusable iterator called
NumberRange.
It should produce numbers from a starting value to an ending value.
class NumberRange:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current <= self.end:
value = self.current
self.current += 1
return value
raise StopIteration
numbers = NumberRange(5, 10)
for number in numbers:
print(number)
5
6
7
8
9
10
Your Tasks
- Change the iterator so it supports a custom step.
- Test a step of 2.
- Add support for counting downward.
- Make sure the iterator stops correctly.
-
Test your iterator with a
forloop. -
Test it manually using
next().
Focus on understanding the iterator protocol,
not just copying the code. You should be able
to explain why __iter__(),
__next__(), and
StopIteration are required.
Knowledge Check
Test your understanding of Python iterators.
1. What is an iterator?
2. Which function obtains an iterator from an iterable?
3. Which function retrieves the next value?
4. Which exception signals the end of an iterator?
5. Which method should return an iterator?
6. Which method produces the next value?
7. What does a for loop use internally?
8. What happens when an iterator is exhausted?
9. What does an iterator remember?
10. Are all iterables themselves iterators?
Lesson Summary
- Iteration means processing values one at a time.
- An iterable is an object from which an iterator can be obtained.
- An iterator produces values one at a time and remembers its current state.
-
iter()obtains an iterator. -
next()retrieves the next value. -
forloops rely on the iterator protocol internally. -
__iter__()returns an iterator. -
__next__()produces the next value. -
StopIterationsignals that the iterator has no more values. - Iterators normally remember their current position.
- An exhausted iterator normally does not restart automatically.
- Custom iterators are useful when you need controlled, stateful iteration behavior.
Excellent Work!
You now understand one of Python's most important internal mechanisms: the iterator protocol.