What is the difference between an iterable and an iterator in Python?
answer
- Two roles, not one word
- One hands out a cursor, one is it
- Which of the two defines __next__?
- iter(x) is x separates them
- collections.abc.Iterable versus collections.abc.Iterator
basics
~20 sAn iterable can hand out a fresh iterator via iter. An iterator is the cursor itself: it defines next to produce one item at a time, and its own iter returns self, so every iterator is also an iterable.
solid answer
~40 sThe two roles differ by protocol. An **iterable** implements `__iter__`, which returns a brand-new iterator each time it is called — `list`, `tuple`, `dict`, `set`, `str` and `range` are iterables. An **iterator** implements `__next__`, which produces the next item and finally signals end of iteration, plus an `__iter__` that returns `self`. That `self` return is the tell: `iter(x) is x` is `True` for an iterator and `False` for a container. Every iterator is therefore an iterable, but not the reverse; `collections.abc.Iterable` and `collections.abc.Iterator` check exactly those methods. The split exists so a container can support many independent cursors at once — two nested loops over one list each get their own position — while an iterator holds the single mutable position and is used up as you read it.
code
python · 8 linesfrom collections.abc import Iterable, Iterator
nums = [1, 2, 3]
cursor = iter(nums)
print(isinstance(nums, Iterable), isinstance(nums, Iterator))
print(isinstance(cursor, Iterable), isinstance(cursor, Iterator))
print(iter(cursor) is cursor, iter(nums) is nums)go deeper
Be ready to state both definitions and give an example of each: a list is an iterable, and the object iter(mylist) returns is an iterator. Adding that every iterator is also an iterable earns the point outright.
Explain the mechanics rather than the labels: __iter__ on a container builds a fresh cursor, __iter__ on an iterator returns self, and __next__ lives only on the iterator. Show the iter(x) is x check and say what collections.abc.Iterator actually tests.
Show the consequence in real code. A function returning an iterator hands the caller a one-shot value, so a retry, a logging pass or a second consumer silently sees nothing. Say when you would return a materialized container instead and how you would document the contract.
Own the interface decision across a codebase: a lazy return buys streaming and bounded memory while pushing single-consumption and lifetime rules onto every caller. Argue when that trade is worth it, and how you keep the convention consistent so callers are not guessing.
### Two cooperating roles, one protocol Python splits "something you can walk through" into two objects that play different roles. An **iterable** is an object that can *produce* a walker. It implements `__iter__`, and the builtin `iter(x)` is what calls it. A list, a tuple, a `str`, a `set`, a `dict`, a `range` object and the views returned by `dict.keys()` are all iterables. An **iterator** is the walker itself. It implements `__next__`, which hands back the next item on each call and raises `StopIteration` once there is nothing left, and it also implements `__iter__` — which returns `self`. That last clause is the entire distinction in one line: `iter(container)` gives you a **new** object; `iter(iterator)` gives you **the same** object back. ### Why the split exists Because a position is state, and state has to live somewhere. A list holds elements; it does not hold "where you are". If a list were its own cursor, then two walks over the same list — a nested loop, a comprehension inside a function called from a loop, a `sum()` followed by a `for` — would fight over one shared position. By making `__iter__` mint a fresh cursor each time, Python lets any number of independent walks proceed over one container at once. Each cursor is cheap: for a list it is little more than a reference to the list plus an integer index. The consequence runs the other way too. When items are produced on demand rather than stored — a generator object, a file being read line by line, a stream arriving over a socket — there is no container behind the walk and no position to rewind to. Such an object *is* the cursor, so its `__iter__` returns `self`, and once consumed it is finished for good. ### Why an iterator must also be an iterable Every construct that consumes items calls `iter()` on its argument first: `for`, comprehensions, `sum()`, `max()`, `min()`, `sorted()`, `list()`, tuple unpacking. If an iterator did not answer that call, none of them could take one directly. Returning `self` makes the call a no-op that costs nothing and, crucially, preserves the position — which is why feeding a half-consumed iterator to a `for` loop **resumes** where it stopped rather than starting over. So the relationship is one-directional: every iterator is an iterable, but most iterables are not iterators. ### Telling them apart at runtime Three checks, in increasing formality: - `iter(x) is x` — `True` only for an iterator. The most direct test, and the one worth remembering. - `hasattr(x, "__next__")` — an iterator defines it; a plain container does not. - `isinstance(x, collections.abc.Iterator)` versus `isinstance(x, collections.abc.Iterable)` — these abstract base classes check structurally for the required methods rather than demanding registration, so they answer correctly for types they have never seen. Note that the `Iterable` check is `True` for both roles; only the `Iterator` check separates them. ```python from collections.abc import Iterable, Iterator nums = [1, 2, 3] cursor = iter(nums) print(isinstance(nums, Iterator), isinstance(cursor, Iterator)) # False True ``` Iterables that are *not* iterators: `list`, `tuple`, `str`, `bytes`, `set`, `frozenset`, `dict` and its views, `range`. Objects that *are* iterators: generator objects, the file object `open()` returns, the result of `enumerate()`, and nearly everything the `itertools` module produces. ### What the protocol deliberately does not promise Iterability is the weakest of Python's data contracts. It says nothing about length, indexing, ordering or repeatability. `len()` does not work on a generator object; `x[3]` does not work on an iterator; and nothing in the protocol lets you ask an iterator whether it has already been consumed — an exhausted one is indistinguishable from an empty source. A sequence adds the stronger promises: integer indexing, `__len__`, slicing. ### Why an interviewer asks it This is not vocabulary trivia; it is the root of a whole family of production bugs. A function that returns an iterator hands its caller a **one-shot value**. If the caller logs it, then loops over it; validates it, then processes it; or passes it to two helpers, the second read sees nothing and raises no error. Deciding whether a public function returns a lazy iterator or a materialized container is therefore a real API design choice: the iterator buys streaming and bounded memory, and it charges the caller with a single-consumption rule that the type system will not enforce for them. Candidates who can state the two definitions, name the `self`-return, and then explain the consequence for calling code are demonstrating three distinct levels of understanding in one answer.
- Why does an iterator have to define `__iter__` at all if it already has `__next__`?So it can be used anywhere an iterable is expected. `for`, comprehensions, `sum()`, `sorted()` and tuple unpacking all call `iter()` on their argument first, and an object that did not answer that call could not be handed to any of them. Returning `self` makes the call a free no-op and preserves the position, which is why passing a half-consumed iterator to a `for` loop resumes rather than restarts.
- Is every iterable also a sequence?No — iterability is the weakest of the contracts. A sequence additionally promises integer indexing, a length via `__len__`, and slicing. A `set` and a `dict` are iterables that are not sequences, and a generator object has neither a length nor indexing. All iterability promises is that you can obtain an iterator and walk the items once through it.
- How would you check at runtime whether an object you were handed is a one-shot cursor?`iter(x) is x` is the direct test: `True` means the object is its own iterator and can be consumed only once. `isinstance(x, collections.abc.Iterator)` is the formal equivalent and reads better in library code, since it checks structurally for both `__iter__` and `__next__` without requiring the type to have registered itself.
A library shelf is the iterable and a bookmark is the iterator: the shelf can issue as many bookmarks as readers ask for, while each bookmark tracks one reader's position and moves only forward.
saying these in an interview costs you the question
- Uses iterable and iterator as interchangeable words
- Claims a list is itself an iterator
- Thinks iter() on a list returns the same object each call
- Believes an iterator can be rewound to the start
- Says only sequences with indexing can be iterated
- Puts __next__ on a container and expects re-iteration