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
| Concept | Iterable | Iterator |
|---|---|---|
| Purpose | Can provide an iterator | Produces the next item on demand |
| Required protocol | Typically implements __iter__() (or sequence protocol) | Implements __iter__() and __next__() |
Call next() directly? | Usually no, e.g., a list | Yes |
| After exhaustion | Can usually create another iterator | next() raises StopIteration |
| Examples | List, tuple, string, dictionary | List 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)beforenext(). - StopIteration: the iterator is exhausted; use a
forloop, 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.

C P Gupta is a YouTuber and Blogger. He is expert in Microsoft Word, Excel and PowerPoint. His YouTube channel @pickupbrain is very popular and has crossed 9.9 Million Views.