What is the difference between an iterable and an iterator in Python?
Short answer
An iterable can produce an iterator, which is the thing that actually yields items one at a time and gets used up. A list can be looped many times, an iterator only once.
The short version
An iterable is anything you can loop over: a list, a string, a dictionary. An iterator is the object that actually walks through it, produced by calling iter() on the iterable. The crucial difference is that an iterator is consumed as you go and cannot be reused, whereas an iterable can be looped over again and again.
Seeing the difference
numbers = [1, 2] # an iterable
it = iter(numbers) # an iterator over it
next(it) # 1
next(it) # 2
next(it) # StopIteration , exhausted
The list is still intact and can be iterated again. The iterator is used up.
for n in numbers: pass # works
for n in numbers: pass # works again
for n in it: pass # yields nothing, it is exhausted
Why this matters in practice
Every for loop calls iter() on the thing you give it, so both work. The trap appears when you hold on to an iterator (or a generator, which is a kind of iterator) and try to use it twice.
squares = (n * n for n in range(3)) # a generator: an iterator
list(squares) # [0, 1, 4]
list(squares) # [] , already consumed!
This silently gives an empty result the second time, with no error, which is one of the more confusing bugs in Python. If you need the values twice, either build a list or recreate the generator.
The same trap with zip, map and filter
In Python 3, zip, map, filter and enumerate all return iterators, not lists.
pairs = zip([1, 2], ["a", "b"])
list(pairs) # [(1, 'a'), (2, 'b')]
list(pairs) # [] , consumed
So a variable holding a zip result is single use. Wrap it in list() if you need it more than once, and be aware that doing so materialises everything in memory, which may be exactly what you were trying to avoid.
Why iterators exist at all
Because they allow lazy, streaming computation: values are produced on demand, so a sequence can be enormous or even infinite without ever being stored. That is what makes it possible to process a file larger than memory, or to chain transformations without building intermediate lists.
The one-shot limitation is the price of that laziness, and it is a reasonable trade once you understand it.
Checking which you have
from collections.abc import Iterable, Iterator
isinstance([1, 2], Iterable) # True
isinstance([1, 2], Iterator) # False , a list is not an iterator
isinstance(iter([1, 2]), Iterator) # True
A useful rule of thumb: if calling len() works, it is a reusable collection, not an iterator. Iterators have no length, because they do not know how many items remain, which is another consequence of their laziness.
Enjoying these?
Go from piecing together answers to real fluency.
Our courses take you from looking things up to doing the work with confidence, step by step on real data.
Browse the courses