What does `yield` actually do?

yield turns the function into a factory for paused stack frames. Calling it runs nothing; each next() runs the body up to the next yield and freezes it there. The sequence is produced on demand, so memory is constant in the length — and it can only be walked once.

Python semanticsConceptualEasy

Step through it

What to watch

  • Calling the function runs none of the body — the state starts at GEN_CREATED.
  • Each resume ends at a yield, which is why the frame is suspended rather than finished.
  • GEN_CLOSED is terminal: iterating again produces nothing, silently.

Say this out loud

"A function with yield returns a generator instead of running. Each next() resumes the body until the next yield, so values are produced lazily and memory stays constant however long the sequence is. The trade is that it is one-shot and has no len() - if I need to iterate twice or index it, I need a list."

Overview

yield returns a value while preserving the function’s execution state for the next request. Calling a generator function creates a lazy iterator; its body runs only as the iterator is advanced.

Run it

The three states, the one-shot exhaustion, and the memory difference at a million items - printed rather than asserted.

The three states, driven by hand.

1Python
Output

One-shot is not a warning, it is silence - the second consumer sees an empty sequence and nothing complains.

2Python
Output

And what the laziness actually buys, in bytes.

3Python
Output

A generator is a paused frame

An ordinary function runs to a return and its stack frame is destroyed. A function containing yield does not run at all when you call it. You get a generator object that owns a frame which has never started.

Every next() resumes that frame, runs until the next yield, hands back the value, and freezes the frame again. Local variables, instruction pointer and all. That is the whole mechanism, and it is why yield can appear in the middle of a loop and still work. The loop variable survives between resumes. This is because the frame was never torn down.

What laziness buys, in one number

The list version of a million squares allocates a million integers and a million pointers before you read the first one. The generator version allocates one object with a frame in it. And the editor below prints both sizes: a few megabytes against about a hundred bytes.

The other half is latency. A generator can hand you its first item at once. This matters when the sequence is being read from a file or a network, and matters absolutely when the sequence is infinite. itertools.count() is a valid generator and an impossible list.

The three things you give up

It is one-shot. Once exhausted, it stays exhausted. And a second for loop over it runs zero times without error. This is the bug people actually hit: passing a generator to two functions and finding the second one saw an empty sequence.

No len(), no indexing, no slicing. The length is not known without running it. So there is nothing to report. itertools.islice is the slicing replacement.

Exceptions surface late. A generator that will raise on its fourth item raises on the fourth next(). This may be a long way from where it was created. Inside a different function, or after a with block has already closed the file it was reading.

The follow-up you should expect

"What is the difference between a generator and an iterator?" An iterator is the protocol. Anything with __next__ and __iter__. A generator function is one common way to implement an iterator; classes and built-in objects can implement the protocol directly too.

And "what does a generator expression change?" Nothing but the syntax: (x*x for x in xs) creates a generator object without building a list. That is why sum(x*x for x in xs) can process the values as it iterates.

Edge cases to raise

Volunteering these is most of what separates a correct answer from a good one.

An exception raised inside the generator surfaces at the next() that triggers it, not where the generator was created - which can be in a different function. After the with block that opened the file has already closed it. Generators that read resources should own the with, not be handed an open handle.

return inside a generator sets StopIteration.value rather than producing an item. yield from reads that value; a plain for loop discards it silently.

Closing matters when there is cleanup. Abandoning a suspended generator in time runs its finally at collection time. This is not a schedule you control. contextlib.closing or an explicit gen.close() makes it deterministic.

Sources and next steps

How the code works

The three states, the one-shot exhaustion, and the memory difference at a million items - printed rather than asserted.

How the code works

  1. g = countdown(3)No output from the body appears on this line. The call builds a generator and returns; the print inside the function has not run.
  2. inspect.getgeneratorstate(g)GEN_CREATED, then GEN_SUSPENDED after a resume, then GEN_CLOSED. The state is the frame's, not the value's.
  3. sum(squares)The second call returns 0. Nothing raised, nothing warned — the generator was already closed, so the loop ran zero times. This is the one-shot bug in its natural habitat.
  4. sys.getsizeof(as_gen)About a hundred bytes, and it does not depend on the million. getsizeof on the list excludes the integers it points at, so the real gap is larger still.

Change one thing

  • Replace list(g) with a second next(g) and watch the state stay GEN_SUSPENDED until the body actually returns.
  • Add len(as_gen). The TypeError is the point: the length is not knowable without running it.
  • Wrap the generator in itertools.tee(as_gen, 2) to get two independent iterators — and note it buffers, so it trades the memory back.

Where this runs

Real CPython, compiled to WebAssembly and running on your own machine — nothing is uploaded. The first run takes a few seconds while the interpreter downloads; after that it is immediate. Need more room, or want to paste your own attempt? Use the Python compiler.

Check yourself

0 of 4

Answer without scrolling back up.

  1. What does calling a function containing yield do?

  2. Why does iterating a generator a second time produce nothing?

  3. The memory advantage of a generator comes from:

  4. Which of these does a generator NOT support?

Cheat sheet

What does `yield` actually do?

Step through a generator function one yield at a time. See the suspended frame, compare memory use to a list, and chain generators with yield from.

INTERVIEW · vizlearn.in/interview/what-does-yield-actually-do.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.