Home Lessons About
Home / Lessons / Iterators
Lesson 50 Advanced Python Concepts

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__().

50–60 minutes Intermediate Iteration
Reading progress 0%
01

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.

You will learn how to:
  • Understand iteration and iterables.
  • Understand the difference between an iterable and an iterator.
  • Use iter() and next().
  • Understand how for loops work internally.
  • Understand StopIteration.
  • Build custom iterators.
  • Use __iter__() and __next__().
  • Understand iterator state.
  • Apply iterators to real-world problems.
02

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.

Basic iteration
numbers = [10, 20, 30, 40]

for number in numbers:
    print(number)
Output
10
20
30
40

The for loop retrieves each value from the collection until there are no more values.

Iterators are one of the mechanisms that make Python's for loops possible.
03

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
Iterable examples
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.

Simple distinction

An iterable can give you an iterator. An iterator actually produces the next values.

04

The iter() Function

The built-in iter() function creates or retrieves an iterator from an iterable.

Creating an iterator
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().

05

The next() Function

The next() function retrieves the next available value from an iterator.

Using next()
numbers = [10, 20, 30]

iterator = iter(numbers)

print(next(iterator))
print(next(iterator))
print(next(iterator))
Output
10
20
30

Every time next() is called, the iterator moves forward.

Calling next() after the iterator has no more values causes StopIteration.
06

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:

Conceptual equivalent
numbers = [10, 20, 30]

iterator = iter(numbers)

while True:

    try:
        number = next(iterator)
        print(number)

    except StopIteration:
        break
You normally use for instead of writing this machinery yourself. Understanding it helps you understand how Python works internally.
07

StopIteration

When an iterator has no more values, it signals that iteration is finished by raising StopIteration.

Example
numbers = [1, 2]

iterator = iter(numbers)

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

print(next(iterator))
Result
1
2
StopIteration

A normal for loop handles this automatically. You generally do not see the exception when using standard iteration.

08

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.

09

__iter__()

The __iter__() method returns an iterator.

For an iterator object, __iter__() normally returns self.

__iter__()
class Counter:

    def __iter__(self):
        return self

Returning self means the object itself is the iterator.

10

__next__()

The __next__() method returns the next value.

When there are no more values, it must raise StopIteration.

Basic __next__()
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.

11

Complete Iterator Class

Let's combine everything into a working iterator.

Counting 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)
Output
1
2
3
4
5

The for loop automatically calls iter() and repeatedly calls next() until StopIteration occurs.

12

Iterator State

An important characteristic of an iterator is that it remembers where it currently is.

Iterator state
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.

Think of an iterator as a bookmark

Every call to next() moves the bookmark forward.

13

Iterators Are Usually One-Time Use

Once an iterator reaches the end, it normally stays exhausted.

Exhausted iterator
numbers = [1, 2, 3]

iterator = iter(numbers)

for number in iterator:
    print(number)

print("Again:")

for number in iterator:
    print(number)
Output
1
2
3
Again:

The second loop has nothing to process because the iterator has already been exhausted.

An iterable such as a list can normally create a fresh iterator whenever needed.
14

Converting Iterables into Iterators

Many Python objects are iterable but are not themselves iterators.

List to iterator
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.

Practical check
values = [1, 2, 3]

iterator = iter(values)

print(next(iterator))
15

Practical Examples

Example 1 — Countdown

Countdown iterator
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)
Output
5
4
3
2
1

Example 2 — Even Numbers

Even number iterator
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)
Output
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.

16

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.

17

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.

18

Mini Challenge — Custom Number Iterator

Build a reusable iterator called NumberRange.

It should produce numbers from a starting value to an ending value.

Challenge starter
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)
Expected Output
5
6
7
8
9
10

Your Tasks

  1. Change the iterator so it supports a custom step.
  2. Test a step of 2.
  3. Add support for counting downward.
  4. Make sure the iterator stops correctly.
  5. Test your iterator with a for loop.
  6. Test it manually using next().
Challenge goal

Focus on understanding the iterator protocol, not just copying the code. You should be able to explain why __iter__(), __next__(), and StopIteration are required.

19

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?

20

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.
  • for loops rely on the iterator protocol internally.
  • __iter__() returns an iterator.
  • __next__() produces the next value.
  • StopIteration signals 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.