Anik Sikder
Technical Writing/python/iterators-pythons-waiters-with-one-way-memory
article.sh

$ open article

python

Iterators in Python Explained: Understanding __next__(), StopIteration, and One-Way Iteration

9 min readSeptember 25, 2025
Python Iterator Waiter Metaphor Visualization

Hey Python developers! 👋

Welcome back to our journey through Python's iteration system.

In Part 1, we explored iterables using a buffet table metaphor.

The buffet contained all the food.

But here's the thing:

You don't eat directly from the buffet.

Someone has to serve you.

That someone is the iterator.

Think of an iterator as a waiter carrying dishes from the buffet to your table.

The waiter remembers:

code
Where they currently are

but forgets everything behind them.

They move in one direction only.

Forward.

Never backward.

In this article, we'll explore:

  • What iterators really are
  • How __iter__() and __next__() work
  • What happens inside memory
  • Why iterators can't rewind
  • Common iterator mistakes
  • Building custom iterators
  • Useful iterator tricks
  • Performance considerations

Let's meet Python's hardest-working waiters. 🍴🤖

Buffet vs Waiter

Let's quickly revisit the metaphor.

Iterable = Buffet Table

Contains all available items.

Example:

code
numbers = [1, 2, 3]

The buffet remains available.

You can revisit it repeatedly.

Iterator = Waiter

Tracks progress.

Example:

code
it = iter(numbers)

The waiter knows:

code
Current position

and serves items one at a time.

Every iterator is temporary.

Once it reaches the end:

code
Job finished.

What Makes an Iterator?

In Python, an iterator is any object that implements:

__iter__()

Returns the iterator itself.

__next__()

Returns the next item.

Or raises:

code
StopIteration

when exhausted.

This is the complete iterator protocol.

A Simple Example

code
nums = [1, 2, 3]

it = iter(nums)

Retrieve values:

code
print(next(it))

Output:

code
1

Again:

code
print(next(it))

Output:

code
2

Again:

code
print(next(it))

Output:

code
3

One more time:

code
print(next(it))

Output:

code
StopIteration

The waiter has no more dishes to serve.

What Does a for Loop Actually Do?

Many developers think:

code
for item in numbers:
    print(item)

is special syntax.

Internally it's approximately:

code
it = iter(numbers)

while True:
    try:
        item = next(it)
        print(item)
    except StopIteration:
        break

Every for loop in Python relies on iterators.

Always.

Understanding StopIteration

This exception is not an error.

It's a signal.

The iterator says:

"I'm finished. Nothing left."

Example:

code
class EmptyIterator:
    def __iter__(self):
        return self

    def __next__(self):
        raise StopIteration

The first call to:

code
next(obj)

immediately ends iteration.

Python uses this mechanism everywhere.

What's Stored Inside an Iterator?

Many beginners imagine iterators copy data.

They don't.

Consider:

code
numbers = [10, 20, 30]

Create:

code
it = iter(numbers)

Typically, the iterator stores:

code
Reference to original list
+
Current index

That's it.

No data duplication.

No extra list creation.

Why Iterators Are Memory Efficient

Imagine:

code
numbers = list(range(10_000_000))

The list consumes significant memory.

But:

code
it = iter(numbers)

adds almost nothing.

The iterator only remembers:

code
Current position

This makes iterators ideal for:

  • Streaming files
  • Processing large datasets
  • Data pipelines
  • Network responses

Why Iterators Can't Rewind

A common question:

Why can't I restart an iterator?

Because iterators don't maintain history.

Imagine a waiter delivering food.

The waiter remembers:

code
Current table

but not every table already visited.

Similarly:

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

tracks only:

code
Current position

Once the end is reached:

code
Finished.

No rewind button exists.

The Classic Iterator Mistake

Consider:

code
it = iter([1, 2, 3, 4])

print(sum(it))

Output:

code
10

Now:

code
print(list(it))

Output:

code
[]

Why?

Because:

code
sum()

already consumed every value.

The waiter already served all dishes.

Nothing remains.

How to Fix Exhausted Iterators

Use the iterable instead.

Example:

code
numbers = [1, 2, 3, 4]

print(sum(numbers))
print(list(numbers))

Output:

code
10
[1, 2, 3, 4]

The iterable can create fresh iterators whenever needed.

Building Your Own Iterator

Let's create a custom iterator.

code
class Counter:
    def __init__(self, limit):
        self.limit = limit
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.limit:
            raise StopIteration

        self.current += 1
        return self.current

Usage:

code
for n in Counter(3):
    print(n)

Output:

code
1
2
3

Our waiter serves three values and retires.

Understanding the Flow

Iteration proceeds like this:

code
Counter
   |
   V
__iter__()
   |
   V
Iterator
   |
   V
__next__()
   |
   V
1
2
3
StopIteration

This is exactly how Python's built-in iterators work.

Iterator Objects Are Usually Their Own Iterators

Notice:

code
def __iter__(self):
    return self

Why?

Because iterator objects already know their own state.

They don't need another iterator.

This differs from iterables like lists.

Example:

code
numbers = [1, 2, 3]

Each call to:

code
iter(numbers)

creates a new iterator.

The iter(callable, sentinel) Trick

One of Python's lesser-known features.

Example:

code
import random

dice = iter(
    lambda: random.randint(1, 6),
    6
)

Now:

code
for roll in dice:
    print(roll)

The callable executes repeatedly.

Iteration stops when:

code
6

is returned.

Think:

code
Keep rolling until six appears.

Very elegant.

Infinite Iterators

Some iterators never stop.

Example:

code
from itertools import count

counter = count()

Produces:

code
0
1
2
3
...

forever.

Use carefully.

Infinite loops happen surprisingly easily.

Controlling Infinite Iterators

Example:

code
from itertools import count, islice

for value in islice(count(), 5):
    print(value)

Output:

code
0
1
2
3
4

islice() acts like a safety fence.

Iterator Pipelines

One of Python's greatest strengths.

Example:

code
numbers = range(10)

Filter:

code
evens = filter(
    lambda x: x % 2 == 0,
    numbers
)

Transform:

code
squares = map(
    lambda x: x * x,
    evens
)

Consume:

code
print(list(squares))

Output:

code
[0, 4, 16, 36, 64]

Each stage processes values lazily.

No giant intermediate lists required.

Lists vs Iterators vs Generators

Choosing the right tool matters.

FeatureListIteratorGenerator
Stores All DataYesNoNo
ReusableYesNoNo
Memory EfficientNoYesYes
Lazy EvaluationNoYesYes
Easy to BuildYesModerateYes

When Should You Use Each?

Use Lists When

  • Multiple passes are needed
  • Random access is important
  • Data fits comfortably in memory

Use Iterators When

  • Processing streams
  • Reading files
  • Building pipelines
  • Working with huge datasets

Use Generators When

  • Creating custom lazy sequences
  • Simplifying iterator implementation

We'll dive deeply into generators in Part 3.

A Visual Mental Model

code
Iterable (Buffet Table)
           |
           |
           | iter()
           V
Iterator (Waiter)
           |
           |
           | next()
           V
      Next Item
           |
           V
    StopIteration

Simple.

Powerful.

Pythonic.

Common Iterator Pitfalls

Sharing One Iterator

Bad:

code
it = iter(data)

consumer1(it)
consumer2(it)

The second consumer may receive partial data.

Assuming Reusability

Bad:

code
it = iter(data)

list(it)
list(it)

Second result:

code
[]

Forgetting Stop Conditions

Infinite iterators without safeguards can lock programs indefinitely.

Always think about termination.

TL;DR Quick Recap

  • Iterators implement __iter__() and __next__().
  • next() retrieves one item at a time.
  • StopIteration signals completion.
  • Every for loop uses iterators internally.
  • Iterators store minimal state.
  • They are memory efficient because they don't copy data.
  • Iterators move forward only.
  • Exhausted iterators cannot be reused.
  • Custom iterators are straightforward to implement.
  • Lazy pipelines make iterators extremely powerful.

Final Thoughts: Iterators Power Almost Everything 🧠

Most Python developers use iterators every day without realizing it.

Whenever you write:

code
for item in something:
    ...

an iterator is quietly working behind the scenes.

It tracks progress.

Delivers values.

Signals completion.

Consumes almost no extra memory.

Understanding iterators unlocks deeper knowledge of:

  • Generators
  • Comprehensions
  • File streaming
  • Data pipelines
  • Async programming
  • Python internals

And speaking of generators...

that's where we're headed next.

A Little Python Joke to End On 😄

Why did the iterator quit its restaurant job?

Because after serving every dish once, it couldn't remember where it started.


Frequently Asked Questions

What is an iterator in Python?

An iterator is an object that returns values one at a time using:

code
__next__()

until:

code
StopIteration

is raised.


What is the difference between an iterable and an iterator?

An iterable can produce iterators.

An iterator tracks progress and produces values.

Think:

code
Iterable → Buffet
Iterator → Waiter

What does next() do?

It requests the next available value from an iterator.

Example:

code
next(iterator)

What is StopIteration?

A special exception that signals the iterator has no more items.

Python uses it internally to stop loops.


Why are iterators memory efficient?

Because they usually store only:

  • A reference to data
  • Current position

rather than duplicating all values.


Why can't iterators be reused?

Because they move forward only.

Once exhausted, they remain finished.


What is iter(callable, sentinel)?

A special form of iter() that repeatedly calls a function until a sentinel value is returned.

Example:

code
iter(func, stop_value)

What are infinite iterators?

Iterators that never naturally terminate.

Example:

code
itertools.count()

Are generators iterators?

Yes.

Generators automatically implement the iterator protocol.

We'll explore them deeply in Part 3.


When should I use iterators?

Use iterators when:

  • Processing large datasets
  • Streaming files
  • Building lazy pipelines
  • Conserving memory

Key Takeaways

  • Iterators are the engines behind Python's iteration system.
  • They implement __iter__() and __next__().
  • StopIteration signals completion.
  • Iterators consume minimal memory.
  • They move forward only and cannot rewind.
  • Custom iterators are easy to build.
  • Iterator pipelines enable scalable data processing.
  • Understanding iterators makes generators and advanced Python concepts much easier to learn.

Series Navigation

Part 1: Iterables — The Buffet Table 🍽️

Part 2: Iterators — The Waiters with One-Way Memory 🍴🤖 (Current Article)

Part 3: Generators — The Lazy Magicians of Python 🎩🐍


If you found this helpful, share it with another Python developer and follow for more Python deep dives, language internals, software architecture discussions, and backend engineering insights. 🚀


About the Author

Anik Sikder is a Software Engineer specializing in Backend Development, Python, Django, FastAPI, DevOps, Cloud Infrastructure, and Software Architecture.

He writes about Python internals, distributed systems, scalable backend platforms, system design, and modern software engineering practices.

$ tags

pythoniteratorsiterationgeneratorspython-internalsstopiterationmemory-optimizationsoftware-engineeringpython-basics

$ ls related_articles

status: end_of_file