Question presented to candidate: "Say you need to process a 50 GB log file: read lines, keep the errors, parse them, and count by type. How would you do it with generators, and why does that not run out of memory?"
What a strong answer should cover:
- A pipeline is a chain of generators, each consuming the previous one:
count(parse(errors(read(f)))). Building the chain runs nothing; the final consumer pulls one item at a time through every stage. - Memory is constant in the input size β only the current item (plus any per-stage state) is alive β versus a list-based version that materialises every intermediate stage.
- Laziness enables infinite sources and early termination:
islice,any,nextandbreakstop pulling, so upstream work that is not needed never happens. - Trade-offs: generators are one-shot, errors surface when an item is pulled (not when the pipeline is built), cleanup needs
close()/with, and per-item Python call overhead makes them slower than a vectorised or list approach for small data. - Tools: generator functions, generator expressions,
itertools(islice,chain,tee), andyield fromfor composing stages.
Clarifying questions expected:
- Does the output need to be ordered, or can stages run in parallel (which generators alone do not give you)?
- Will the data be read once or several times (one-shot iterators vs. re-reading the source)?
Code / implementation expected: Yes β a three- or four-stage generator pipeline over a file-like source, ideally showing that items flow one at a time.