Question presented to candidate:
"Forget the syntax for a second. When Python runs for x in something:, what actually happens, step by step? What does the interpreter call, how does it know when to stop, and what does that explain about bugs like modifying a list while you loop over it?"
What a strong answer should cover:
- The loop calls
iter(something)once (which usestype(something).__iter__, or the__getitem__fallback), then callsnext()on the result before every pass, assigning the value to the loop target - The loop ends when
__next__raisesStopIteration; the loop catches it, so the user never sees it, and then theelseclause runs if there was nobreak - It is equivalent to a
while Trueloop withtry: x = next(it) except StopIteration: break; CPython compiles it toGET_ITERplus aFOR_ITER/JUMP_BACKWARDloop (END_FORadded in 3.12) - The loop target is an ordinary assignment: it can unpack, it keeps its last value after the loop, and rebinding it in the body does not affect the next item
- Consequences: a list iterator is just an index, so removing items while looping skips elements; dicts and sets raise
RuntimeErrorif their size changes; a generator can only be looped once forworks on any iterable, not only sequences: no indexes are involved, which is why it works on files, generators and infinite streams
Code / implementation expected: Optional β rewriting a for loop as the equivalent while loop with iter(), next() and StopIteration.