Iterators with yield return

The yield return keyword lets you build a sequence lazily — producing one element at a time, exactly when it's needed, without ever building the whole collection in memory. The compiler turns your simple loop into a sophisticated state machine behind the scenes. Master this and you'll understand how LINQ works, how to stream huge data sets, and even how to model infinite sequences safely.

Learn Iterators with yield return in our free C# course — an interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free C# course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Think of a vending machine versus a catering tray . The catering tray (a List T ) makes every sandwich up front and lays them all out, whether anyone eats them or not. The vending machine (a yield iterator) makes nothing until you press a button — then it produces exactly one item, and waits. Press again, get the next. You can walk away after two items and the machine never wasted effort on the rest. That on-demand, one-at-a-time delivery is lazy evaluation , and it's the whole point of yield return .

A normal method runs top to bottom and returns once. An iterator method is different: each yield return hands back a value and pauses , remembering exactly where it stopped. The next time a value is requested, it resumes right after that line.

Because it produces values on demand, an iterator can describe an infinite sequence (every whole number, the Fibonacci series) and still be perfectly safe — the consumer simply stops pulling when it has enough.

1. Your First Iterator: yield return

An iterator method returns IEnumerable T but never builds a collection. Each yield return gives the caller one value and pauses. Run the worked example below and watch the "producing" lines interleave with the "got" lines — that interleaving is the proof that execution pauses and resumes rather than running all at once.

2. Lazy (Deferred) Evaluation

Calling an iterator method does not run its body — it just returns an enumerable. The body only executes when you begin enumerating, and only as far as you pull. That's why an iterator can loop forever: paired with Take(5) , the consumer stops it after five items.

Your turn. Complete the squares generator by writing the two keywords that produce each value. Fill in the ___ blanks.

3. Ending Early with yield break

Sometimes you want to stop the sequence partway through — when you hit a sentinel value, an error, or a condition. yield break ends the iteration immediately: no further items are produced, exactly like returning from a normal method. The example stops emitting as soon as it meets a negative number.

4. The Generated State Machine

You write a simple sequence; the compiler rewrites it into a hidden class that implements IEnumerator T and remembers where execution paused between items. foreach normally hides this, but driving the enumerator by hand with MoveNext() and Current reveals the machinery underneath.

Now you try: take a fixed count from an infinite Fibonacci iterator. Fill in the LINQ method that limits how many items you pull.

Both of these methods can be consumed with foreach , but they behave very differently:

The List version allocates all n items immediately, even if the caller only reads the first one. The yield version produces items as needed, so it uses less memory, starts returning results sooner, and can even be infinite. The trade-off: a List can be re-read and indexed cheaply, while an iterator re-runs from the start each time you enumerate it.

Here's the pattern behind processing a huge file without loading it all: an iterator streams lines one at a time, and LINQ's Where and Select — themselves lazy iterators — filter and transform them on demand. Only the matching lines are ever materialized.

Because the whole pipeline is lazy, you could point ReadLines at a multi-gigabyte file and still keep memory flat — that's the power of composing iterators.

Q: When does an iterator's body actually run?

Only when you enumerate it — the first foreach iteration or MoveNext() call. Just calling the method returns the enumerable without running any of the body. This is deferred (lazy) execution.

Q: What's the difference from returning a List T ?

A List builds every element up front; a yield iterator streams them one at a time on demand. The iterator can be infinite and uses less memory, but re-enumerates from scratch each time and can't be indexed.

Q: How can an infinite iterator not hang the program?

Because items are produced lazily, the consumer decides when to stop. Pair an infinite generator with Take(n) or First() and it produces only as many values as you ask for.

Yes — most LINQ operators ( Where , Select , Take ) are deferred iterators that yield results as you enumerate. That's why a LINQ query runs at the point you iterate it, not where you define it.

No blanks this time — just a brief and an outline. Write an infinite Evens() iterator with yield return , then use Take(5) to print the first five even numbers. For a bonus, chain Where and Take and confirm it stays lazy. Run it and check your output against the expected lines.

Practice quiz

What does a yield return statement do inside an iterator method?

  • Produces one element and pauses the method until the next item is requested
  • Returns the whole collection at once and exits
  • Starts a new thread
  • Throws an exception

Answer: Produces one element and pauses the method until the next item is requested. yield return hands one element back to the caller and pauses execution, resuming from that exact point when the next value is requested.

What return type can a method using yield return have?

  • Only List<T>
  • void
  • IEnumerable<T>, IEnumerator<T>, or their non-generic forms
  • Task<T>

Answer: IEnumerable<T>, IEnumerator<T>, or their non-generic forms. An iterator method must return IEnumerable, IEnumerable<T>, IEnumerator, or IEnumerator<T>. The compiler builds a state machine that implements it.

Iterators built with yield use what kind of evaluation?

  • No evaluation at all
  • Lazy (deferred) evaluation — work happens as items are pulled
  • Eager evaluation — everything runs immediately
  • Parallel evaluation across cores

Answer: Lazy (deferred) evaluation — work happens as items are pulled. yield iterators are lazy: the method body doesn't run until you start enumerating, and it produces each item only when asked.

What does yield break do?

  • Skips the current item
  • Restarts the sequence
  • Pauses for one item
  • Ends the iteration early, like a return with no more items

Answer: Ends the iteration early, like a return with no more items. yield break stops the iterator: enumeration ends and no further items are produced, similar to returning from a normal method.

How does the C# compiler implement an iterator method under the hood?

  • By generating a hidden state machine class that implements IEnumerator
  • By copying everything into a List
  • By spawning background tasks
  • By using reflection at runtime

Answer: By generating a hidden state machine class that implements IEnumerator. The compiler rewrites the method into a generated state machine class that tracks where execution paused and resumes there on the next MoveNext call.

Why can a yield iterator represent an INFINITE sequence safely?

  • It cannot — infinite sequences are illegal
  • Because items are produced lazily and the consumer decides when to stop
  • It pre-computes all values
  • Because C# limits it to 1000 items

Answer: Because items are produced lazily and the consumer decides when to stop. Since values are produced on demand, an infinite generator like all whole numbers is fine as long as the consumer stops pulling (e.g. with Take).

Compared with building and returning a List<T>, a yield iterator typically:

  • Runs only on a background thread
  • Cannot be used with foreach
  • Always uses more memory
  • Avoids allocating the whole collection up front, streaming items one at a time

Answer: Avoids allocating the whole collection up front, streaming items one at a time. A List materializes every element immediately; a yield iterator streams items lazily, so it can save memory and start producing results sooner.

When you call an iterator method, when does its body first start running?

  • When the program exits
  • Never
  • When you begin enumerating it (e.g. the first MoveNext / foreach iteration)
  • Immediately when the method is called

Answer: When you begin enumerating it (e.g. the first MoveNext / foreach iteration). Calling the method just returns the enumerable; the body doesn't execute until enumeration begins. This deferred start is a hallmark of yield iterators.

Which interface does foreach ultimately rely on to walk a yield iterator?

  • IDisposable
  • IEnumerator<T> (obtained via IEnumerable<T>.GetEnumerator)
  • ICloneable
  • IComparable

Answer: IEnumerator<T> (obtained via IEnumerable<T>.GetEnumerator). foreach calls GetEnumerator to get an IEnumerator<T>, then repeatedly calls MoveNext and reads Current — exactly what the generated state machine provides.

How do LINQ operators like Where and Select relate to yield iterators?

  • Many are themselves lazy iterators that yield results as you enumerate
  • They always return arrays immediately
  • They disable deferred execution
  • They are unrelated to iterators

Answer: Many are themselves lazy iterators that yield results as you enumerate. Most LINQ query operators are implemented as deferred iterators; they yield items one at a time as you enumerate, which is why the query runs when you iterate it.