Iterators and `yield`
An iterator is a method (or property/accessor) that returns IEnumerable<T> or IEnumerator<T> and uses yield return to produce values one at a time.
Concept
An iterator is a method (or property/accessor) that returns IEnumerable<T> or IEnumerator<T> and uses yield return to produce values one at a time.
yield return value; hands one value to the consumer and pauses the method; the next MoveNext() resumes it right after.
yield break; ends the sequence.
The compiler rewrites the method into a hidden state-machine class (you can see its generated name in the example) that implements IEnumerable<T> and IEnumerator<T>.
Execution is lazy: nothing runs until enumeration starts, and only as many values are produced as the consumer asks for — so infinite sequences like Fibonacci() are fine when combined with Take.
foreach calls GetEnumerator(), then MoveNext() / Current repeatedly, and disposes the enumerator at the end.
Why It Matters
Iterators let you stream large or infinite data (log files, sensor streams, paging through APIs) with constant memory, and they are how LINQ operators themselves are implemented.
Syntax
static IEnumerable<T> Name(...)
{
...
yield return item;
...
yield break;
}Example
// yield return builds a lazy sequence; the method body runs on demand
foreach (int n in Numbers(5))
Console.Write($"{n} ");
Console.WriteLine();
// Laziness: only the values actually requested are produced
foreach (long f in Fibonacci().Take(10))
Console.Write($"{f} ");
Console.WriteLine();
// Execution is interleaved with the consumer
foreach (string frame in ReadFrames(["18FEF100#01", "bad line", "0CF00400#FF", "STOP", "18FEF100#02"]))
Console.WriteLine($" consumer got {frame}");
// Manual enumeration: what foreach does under the hood
IEnumerator<int> e = Numbers(3).GetEnumerator();
while (e.MoveNext())
Console.Write($"[{e.Current}]");
Console.WriteLine();
Console.WriteLine($"iterator class generated by the compiler: {Numbers(1).GetType().Name}");
static IEnumerable<int> Numbers(int count)
{
for (int i = 0; i < count; i++)
yield return i;
}
static IEnumerable<long> Fibonacci()
{
long a = 0, b = 1;
while (true) // infinite, but lazy
{
yield return a;
(a, b) = (b, a + b);
}
}
static IEnumerable<string> ReadFrames(IEnumerable<string> lines)
{
foreach (string line in lines)
{
if (line
…Expected Result
The producer and consumer lines interleave: each frame is yielded, consumed, and only then does the producer continue. After yield break, the last frame is never read.
0 1 2 3 4
0 1 1 2 3 5 8 13 21 34
producer: yielding 18FEF100#01
consumer got 18FEF100#01
producer: skipping 'bad line'
producer: yielding 0CF00400#FF
consumer got 0CF00400#FF
producer: STOP seen, yield break
[0][1][2]
iterator class generated by the compiler: <<<Main>$>g__Numbers|0_0>dCommon Mistakes
Expecting argument validation in an iterator to run at the call — it runs only when enumeration starts (wrap it in a non-iterator method that validates, then calls a private iterator).
Enumerating an expensive iterator several times.
Using ref/out parameters in iterators (not allowed) or yield inside try blocks that have a catch (not allowed).
Forgetting that code after the loop runs only if the consumer enumerates to the end.
Best Practices
Use iterators for streaming and lazy pipelines; return materialized collections when callers need random access or multiple passes.
Validate arguments eagerly with the wrapper pattern.
Dispose resources in finally/using inside iterators — they run when the consumer stops early too.
Interview Relevance
Medium: "how does yield return work?", "what is deferred execution?", "implement a custom Where with yield".
Practice Questions
Write IEnumerable<int> Range(int start, int count, int step).
Implement your own Where<T> and Select<T, TResult> with yield.
Write an iterator that reads a file line by line and yields only valid CAN frames.
Produce an infinite sequence of prime numbers and take the first 20.
Quick Quiz
Q1. (True/False) The body of an iterator method runs when the method is called.
Answer: False. It runs when the sequence is enumerated.
Q2. (Multiple choice) Which statement ends an iterator early? A) return; B) yield break; C) break; D) yield return null; Answer: B.
Q3. (Predict the output) Fibonacci().Take(10) — what is the 10th number?
Answer: 34 (verified: 0 1 1 2 3 5 8 13 21 34).
Q4. (Find the bug) An iterator throws ArgumentNullException for a null argument, but the exception appears far from the call. Why?
Answer: Iterator bodies run lazily; validate in a non-iterator wrapper method.
Coding Challenge
Write IEnumerable<T[]> Batch<T>(IEnumerable<T> source, int size) that yields arrays of up to size items (validate size eagerly).
foreach (int[] batch in Batch(Enumerable.Range(1, 7), 3))
Console.WriteLine(string.Join(",", batch));
static IEnumerable<T[]> Batch<T>(IEnumerable<T> source, int size)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(size); // runs immediately
return Iterator(source, size);
static IEnumerable<T[]> Iterator(IEnumerable<T> source, int size)
{
var buffer = new List<T>(size);
foreach (T item in source)
{
buffer.Add(item);
if (buffer.Count == size) { yield return buffer.ToArray(); buffer.Clear(); }
}
if (buffer.Count > 0) yield return buffer.ToArray();
}
}