Skip to content

Python Iterables vs Iterators: iter(), next(), Generators and Examples

An iterable is an object you can loop over, such as a list, string, tuple or dictionary. An iterator is an object that remembers where it is in a sequence and returns its next item when you call next(). Python’s for loop uses this protocol automatically, which is why iterators are central to generators, file processing, comprehensions, and many data pipelines.

Iterable vs iterator: the key difference

ConceptIterableIterator
PurposeCan provide an iteratorProduces the next item on demand
Required protocolTypically implements __iter__() (or sequence protocol)Implements __iter__() and __next__()
Call next() directly?Usually no, e.g., a listYes
After exhaustionCan usually create another iteratornext() raises StopIteration
ExamplesList, tuple, string, dictionaryList iterator, generator, file iterator

The previous tutorial mixed up built-in iterables and actual iterator objects. A list, for example, is iterable, but next(my_list) raises TypeError. Instead, call iter(my_list) to obtain an iterator and then call next() on the result.

Example 1: iter() and next() with a list

numbers = [10, 20, 30]         # iterable
iterator = iter(numbers)       # iterator

print(next(iterator))          # 10
print(next(iterator))          # 20
print(list(iterator))          # [30]
print(list(iterator))          # []: now exhausted

The first two calls advance the same iterator; converting the remainder to a list consumes its last value. Once exhausted, that iterator cannot be restarted. A new iter(numbers) creates a fresh iterator from the original list.

Example 2: StopIteration and next(iterator, default)

empty = iter([])

print(next(empty, "No items"))  # No items

try:
    print(next(empty))
except StopIteration:
    print("Iterator is exhausted")

When you call next(iterator) after exhaustion, Python raises StopIteration. With next(iterator, default), it returns the supplied default value instead. A for loop catches StopIteration internally and finishes normally.

What happens inside a for loop?

words = ["PickupBrain", "Learn", "Python"]

# Standard code
for word in words:
    print(word)

# Roughly equivalent manual iteration
it = iter(words)
while True:
    try:
        word = next(it)
    except StopIteration:
        break
    print(word)

The for version is easier to read and correctly handles the iteration protocol. Manual next() is useful when you need fine control over consuming an iterator.

Example 3: Write a custom Python iterator

An iterator object implements __iter__() to return itself and __next__() to return a value or raise StopIteration. The following example returns odd numbers less than a chosen limit. It was executed and checked locally.

class OddNumbers:
    def __init__(self, stop):
        self.stop = stop
        self.current = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.stop:
            raise StopIteration
        value = self.current
        self.current += 2
        return value

print(list(OddNumbers(10)))  # [1, 3, 5, 7, 9]

Unlike the older sample, this code does not reset its position inside __iter__(). Resetting state every time iter()` is called on the same iterator can cause surprising repetitions. If you want a re-iterable container, give it an <code>__iter__() method that creates a new iterator instead.

Example 4: Use a generator instead of writing a class

def odd_numbers(stop):
    current = 1
    while current < stop:
        yield current
        current += 2

print(list(odd_numbers(10)))  # [1, 3, 5, 7, 9]

squares = (x * x for x in range(3))
print(list(squares))          # [0, 1, 4]

A generator function uses yield to produce values as needed. Generator expressions likewise create iterators. They can avoid building an entire result list at once, although any upstream data already stored in memory still occupies memory.

Are dictionaries and strings iterables?

print(list(iter("cat")))  # ["c", "a", "t"]

person = {"name": "Joe", "dept": "Engineering"}
print(list(iter(person)))  # ["name", "dept"] (dictionary keys)

Iteration over a dictionary yields its keys by default. Use person.values() or person.items() to iterate over values or key/value pairs.

Typical errors and fixes

  • TypeError: list object is not an iterator: call iter(your_list) before next().
  • StopIteration: the iterator is exhausted; use a for loop, a default value, or create a new iterator where appropriate.
  • Only part of a sequence appears: an earlier function may already have consumed items from the iterator.
  • Repeated or infinite items: inspect custom __iter__() and __next__() logic; ensure there is a definite stopping condition.
  • Large datasets: iterators and generators can be memory-friendly, but converting them to list() materializes their values in memory.

When should you use an iterator?

Use an ordinary for loop for everyday collection processing. Choose iterators and generators when you need incremental consumption, want to represent a potentially long or unbounded stream, or must integrate with an API that accepts iterables. For related Python basics, see our list comprehension tutorial.

Summary

The simplest rule is: an iterable can produce an iterator; an iterator can produce the next value. Use iter() to obtain an iterator, next() to advance it, and for when you want Python to handle iteration for you. See the official Python iterator tutorial for the protocol behind these examples.

Leave a Reply

Your email address will not be published. Required fields are marked *