When NOT to use yield return
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
yield return is powerful in C# because it turns a method into a lazy iterator with very little code. The problem is that laziness changes execution timing and state lifetime, so there are cases where yield return is elegant and cases where it makes behavior harder to reason about.
What yield return Buys You
With yield return, values are produced on demand as the consumer enumerates:
This is great when:
- the sequence can be streamed
- not every item will be consumed
- materializing the whole result would waste memory
But those benefits come with tradeoffs.
When a Materialized Collection Is Better
If the consumer always needs the full result immediately, a List<T> can be simpler and sometimes faster overall:
A list gives predictable one-time execution and allows indexing, counting, and repeated iteration without recomputing.
Avoid yield return When Re-Execution Is a Problem
Iterator methods run each time the sequence is enumerated. That is fine for pure computations, but dangerous for code with side effects or expensive external work.
Example:
If a caller enumerates this sequence twice, the file is read twice. That may be correct, or it may be a costly surprise. If the data should be captured once, materialize it eagerly.
Avoid It for Random Access or Snapshot Semantics
Lazy iterators are forward-oriented. If callers need:
- indexing
- repeated counting
- stable snapshot semantics
then a concrete collection is usually the better return type.
This is especially true when the underlying data source may change between enumerations. A yield return sequence often represents a live computation, not a frozen result.
Error Timing Matters
With yield return, exceptions may occur during enumeration rather than at method call time. That can make debugging and API expectations more confusing.
For example, a method returning IEnumerable<T> may appear to succeed immediately, but fail only when the caller starts iterating. Sometimes that is acceptable. Sometimes it hides failures too late in the control flow.
Disposal and Resource Lifetime
Iterators can hold resources open for as long as enumeration continues. That is useful in streaming scenarios, but bad if callers may abandon enumeration early or keep the enumerator alive longer than expected.
If predictable resource release matters more than streaming, materializing inside a using block and returning a concrete result may be safer.
Common Pitfalls
The biggest mistake is using yield return for sequences with side effects, then being surprised when multiple enumeration repeats those side effects.
Another mistake is returning a lazy sequence when the API contract really needs a stable snapshot. Consumers may assume the result is fixed when it is actually recomputed each time.
A third issue is ignoring exception timing. Iterator methods often delay work, which delays failures too.
Summary
- '
yield returnis excellent for lazy, forward-only streaming.' - Do not use it when callers need random access, snapshot semantics, or guaranteed one-time execution.
- Multiple enumeration can repeat expensive work or side effects.
- Exceptions and resource lifetime become deferred, which is not always desirable.
- If predictability matters more than laziness, return a concrete collection instead.

