Iterable produces iterators; iterators implement iter and next
An iterable is any object that can produce an iterator: it implements iter, which returns a fresh iterator. An iterator is the object that does the actual work: it implements next, which returns the next value or raises StopIteration to signal exhaustion, and iter, which returns self so that an iterator is itself iterable. The for loop is just sugar: Python calls iter(obj) once, then next() repeatedly until StopIteration is raised. The critical design consequence is that iterators are stateful and single-use, while well-behaved iterables are re-iterable because each iter call returns a new iterator.
Iterable: defines iter (or the legacy getitem fallback). Lists, dicts, sets, strings, and file objects are iterables.
Iterator: defines next and iter returning self. Generators, map/filter/zip results, and reversed objects are iterators.
Trade-off: one-shot iterators are memory-efficient and lazily evaluated, but if you need two passes you must either keep the iterable and call iter() again or materialize into a list, which trades memory for reusability.
Common mistake: implementing only next on a class. The for loop calls iter() first, so a next-only object raises TypeError: object is not iterable.
Common mistake: returning self from iter on a container class, which makes the second loop over the same object silently empty.
Version note: PEP 234 defined the iter/next protocol. The next method was called next() in Python 2, and the getitem iteration fallback still works but is legacy.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience