Question

What the yield Keyword Does in C#: Iterators and Lazy Enumeration

csharpyield

Question

In C#, I saw this method used in an answer about exposing only part of an IList<>:

IEnumerable<object> FilteredList()
{
    foreach (object item in FullList)
    {
        if (IsItemInPartialList(item))
            yield return item;
    }
}

What does the yield keyword do in this method?

I have seen yield mentioned in a few places, but I do not yet understand what it actually does. I am familiar with the word yield in the sense of one thread yielding to another, but that does not seem to apply here. How does yield return work in C#?

Short Answer

By the end of this page, you will understand that yield in C# is used to create iterators—methods that produce values one at a time instead of building and returning a whole collection immediately. You will learn how yield return works, why it enables lazy evaluation, how enumeration resumes from where it left off, and when to use it in real code.

Concept

In C#, the yield keyword is used inside a method, property, or operator to return elements of a sequence one at a time.

The most common form is:

yield return value;

When a method contains yield return, it becomes an iterator method. Instead of running all the code at once and returning a fully built collection, C# transforms the method into something that can be paused and resumed during enumeration.

That means:

  • The method returns an IEnumerable<T> or IEnumerator<T> sequence.
  • Each yield return produces one item.
  • Execution pauses after returning that item.
  • The next time the caller asks for another item, execution continues from the same place.

For example, this method:

IEnumerable<int> GetNumbers()
{
    yield return 1;
    yield return 2;
    yield return 3;
}

behaves like a sequence that produces 1, then 2, then 3 as it is iterated.

This matters because it lets you:

  • avoid creating temporary lists
  • work efficiently with large data sets
  • filter data on demand
  • express custom iteration logic clearly

In your example:

IEnumerable<object> FilteredList()
{
    foreach (object item in FullList)
    {
        if (IsItemInPartialList(item))
            yield return item;
    }
}

FilteredList() does not build a new list immediately. Instead, it creates a sequence that checks each item in FullList as the caller iterates, and only yields the matching ones.

There is also:

yield break;

which stops the sequence early.

Mental Model

Think of yield return like a librarian handing you books one at a time from a shelf.

  • A normal return is like putting all requested books into a box and handing you the whole box at once.
  • A yield return is like giving you one book, waiting until you ask again, then continuing from the exact place on the shelf where the librarian stopped.

So the method does not finish all at once. It pauses, remembers its position, and continues later.

That is the key idea behind iterators in C#:

  • return = give back one final result and end the method
  • yield return = give back one element of a sequence and pause
  • yield break = stop producing more elements
Take Quiz

Syntax and Examples

The basic syntax looks like this:

IEnumerable<int> GetValues()
{
    yield return 10;
    yield return 20;
    yield return 30;
}

You can use it with foreach:

foreach (int value in GetValues())
{
    Console.WriteLine(value);
}

Output:

10
20
30

Example: filtering values

IEnumerable<int> GetEvenNumbers(List<int> numbers)
{
    foreach (int number in numbers)
    {
        if (number % 2 == 0)
        {
            yield return number;
        }
    }
}

Usage:

var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

foreach (int even in GetEvenNumbers(numbers))
{
    Console.WriteLine(even);
}

Output:

2
4
6

Why this is useful

Without yield, you might write:

List<int> GetEvenNumbers(List<int> numbers)
{
    List<int> result = new List<int>();

    foreach (int number in numbers)
    {
        if (number % 2 == 0)
        {
            result.Add(number);
        }
    }

    return result;
}

This works, but it builds the whole list before returning. The yield version produces values only when needed.

Example with yield break

IEnumerable<int> CountUpTo(int max)
{
    for (int i = 1; i <= max; i++)
    {
        if (i > 5)
            yield break;

        yield return i;
    }
}

This stops the sequence once i becomes greater than 5.

Step by Step Execution

Consider this method:

IEnumerable<string> GetShortWords()
{
    string[] words = { "cat", "elephant", "dog", "giraffe" };

    foreach (string word in words)
    {
        if (word.Length <= 3)
        {
            yield return word;
        }
    }
}

And this code:

foreach (string word in GetShortWords())
{
    Console.WriteLine(word);
}

Here is what happens step by step:

  1. GetShortWords() is called.
  2. The method does not immediately loop through all words and print anything.
  3. It returns an enumerable object that knows how to produce values later.
  4. The foreach starts iterating.
  5. Execution enters the iterator method.
  6. word = "cat"
    • length is 3
    • yield return word; returns "cat"
    • execution pauses here
  7. foreach prints cat
  8. foreach asks for the next item
  9. Execution resumes after the previous yield return
  10. word = "elephant"
    • length is greater than 3
    • nothing is yielded
  11. word = "dog"
    • length is 3
    • yield return word; returns "dog"
    • execution pauses again
  12. foreach prints dog
  13. Iteration resumes again
  14. word = "giraffe"
    • skipped
  15. End of method is reached
  16. Enumeration finishes

Output:

cat
dog

The important detail is that the method resumes from the point where it last yielded a value.

Real World Use Cases

yield is useful anywhere you want to produce a sequence gradually.

Filtering collections

Your example is a classic case: expose only items that match a rule.

IEnumerable<Order> GetOpenOrders(IEnumerable<Order> orders)
{
    foreach (var order in orders)
    {
        if (!order.IsClosed)
            yield return order;
    }
}

Reading large files line by line

Instead of loading everything into memory at once, you can yield each line.

IEnumerable<string> ReadImportantLines(string path)
{
    foreach (var line in File.ReadLines(path))
    {
        if (line.Contains("ERROR"))
            yield return line;
    }
}

Generating computed data

IEnumerable<int> Squares(int count)
{
    for (int i = 1; i <= count; i++)
    {
        yield return i * i;
    }
}

Tree or graph traversal

IEnumerable<Node> Traverse(Node root)
{
    yield return root;

    foreach (var child in root.Children)
    {
        foreach (var node in Traverse(child))
        {
            yield return node;
        }
    }
}

API and data processing pipelines

Developers use iterators to create sequences that can later be filtered, mapped, or consumed by LINQ.

Take Quiz

Real Codebase Usage

In real projects, yield often appears in helper methods that expose data as IEnumerable<T> without forcing immediate allocation.

Common patterns

1. Guard clauses before iteration

IEnumerable<string> GetNames(List<User> users)
{
    if (users == null)
        yield break;

    foreach (var user in users)
    {
        if (!string.IsNullOrWhiteSpace(user.Name))
            yield return user.Name;
    }
}

This avoids errors and cleanly returns an empty sequence when input is invalid.

2. Validation and filtering

IEnumerable<Product> GetAvailableProducts(IEnumerable<Product> products)
{
    foreach (var product in products)
    {
        if (product.Stock > 0 && product.IsActive)
            yield return product;
    }
}

3. Wrapping existing collections with custom rules

Instead of exposing a raw list, teams often expose an enumerable with conditions, ordering, or transformation.

IEnumerable<string> GetVisibleMessages(IEnumerable<Message> messages)
{
    foreach (var message in messages)
    {
        if (!message.IsDeleted)
            yield return message.Text;
    }
}

4. Replacing manual enumerator classes

Without yield, implementing IEnumerable<T> manually can be verbose. yield makes custom iteration much simpler.

5. Working with LINQ-style pipelines

Iterator methods fit naturally with LINQ because both work with IEnumerable<T>.

var result = GetAvailableProducts(products)
    .Where(p => p.Price < 100)
    .Select(p => p.Name);

Important practical note

Because yield is lazy, the underlying data is read when the sequence is enumerated, not when the method is called. That can be useful, but it also means results may change if the source collection changes before enumeration.

Common Mistakes

1. Expecting the method to run immediately

Beginners often think this code executes everything at call time:

var result = GetEvenNumbers(numbers);

Usually, the body does not fully run until you iterate:

foreach (var n in result)
{
    Console.WriteLine(n);
}

2. Confusing yield return with normal return

Broken idea:

IEnumerable<int> GetNumbers()
{
    return 1;
}

This is invalid because the method must return a sequence, not a single int.

Correct:

IEnumerable<int> GetNumbers()
{
    yield return 1;
}

3. Assuming the result is a stored list

This may surprise you:

var sequence = GetEvenNumbers(numbers);
numbers.Add(8);

If the sequence has not been enumerated yet, 8 may appear during iteration because the method runs later.

If you need a fixed snapshot, materialize it:

var list = GetEvenNumbers(numbers).ToList();

4. Modifying the collection during enumeration

Broken example:

foreach (var item in numbers)
{
    numbers.Add(99);
}

This can throw an exception. Be careful when the iterator reads from a collection that may change while being enumerated.

5. Forgetting that multiple enumeration reruns the iterator

var sequence = GetEvenNumbers(numbers);

foreach (var n in sequence) { }
foreach (var n in sequence) { }

The iterator logic runs again each time. If the work is expensive, store the results:

var cached = GetEvenNumbers(numbers).ToList();

6. Using yield when a simple list is clearer

yield is useful, but not always necessary. If you already need all results immediately and will reuse them often, returning a list may be simpler.

Comparisons

ConceptWhat it doesWhen to use it
returnEnds the method and returns one final resultWhen you already have the complete result
yield returnProduces one item of a sequence and pausesWhen returning items one at a time
yield breakStops an iterator earlyWhen no more items should be produced

yield vs building a list

ApproachBehaviorProsCons
Build and return List<T>Computes everything firstSimple, reusable, fixed snapshotUses more memory, does all work up front
Use yield returnComputes items as neededLazy, memory-friendly, expressiveCan rerun logic, depends on source state at iteration time

yield vs LINQ

ApproachBest for
yield returnWriting your own custom sequence logic
LINQ (Where, Select)Applying common filtering and projection operations

Example:

IEnumerable<int> GetEvenNumbersManual(IEnumerable<int> numbers)
{
    foreach (var n in numbers)
    {
        if (n % 2 == 0)
            yield return n;
    }
}

Similar LINQ version:

IEnumerable<int> GetEvenNumbersLinq(IEnumerable<int> numbers)
{
    return numbers.Where(n => n % 2 == 0);
}

Both are lazy. yield is more flexible when the logic is more complex.

Take Quiz

Cheat Sheet

IEnumerable<T> MethodName()
{
    yield return item1;
    yield return item2;
    yield break;
}

Rules

  • yield return returns one element at a time.
  • yield break stops the sequence.
  • Iterator methods usually return IEnumerable<T> or IEnumerator<T>.
  • Execution is lazy: it happens during enumeration.
  • Each new enumeration starts the iterator again from the beginning.

Typical pattern

IEnumerable<int> Filter(IEnumerable<int> values)
{
    foreach (var value in values)
    {
        if (value > 0)
            yield return value;
    }
}

Remember

  • return ends the method completely.
  • yield return pauses and resumes later.
  • Use .ToList() if you need a concrete list right now.
  • Source collection changes can affect future enumeration.

Common use cases

  • filtering
  • generating sequences
  • reading data lazily
  • custom traversal logic

FAQ

What does yield return mean in C#?

It means the method is returning one item of a sequence and then pausing, so it can continue later when the next item is requested.

Does yield return create a list?

No. It creates a lazy sequence. Items are usually produced only when you iterate over them.

What is the difference between return and yield return?

return sends back one final result and ends the method. yield return sends back one sequence element and pauses the method.

When does a yield method actually execute?

Usually when the returned sequence is enumerated, such as in a foreach loop.

What does yield break do in C#?

It stops the iterator immediately, producing no more items.

Is yield return efficient?

Often yes, especially for large sequences, because it avoids building a whole collection up front.

Can I use yield for filtering data?

Yes. Filtering is one of the most common uses of yield return.

Why would results change between calls?

Because iterator methods are lazy. If the underlying data changes before enumeration, the produced items may also change.

Take Quiz

Mini Project

Description

Build a small iterator method that filters a collection of names and returns only valid entries. This demonstrates how yield return can expose a clean, lazy sequence without creating a new list manually.

Goal

Create a method that lazily returns only non-empty names with at least three characters, then iterate over the results.

Requirements

  • Create a C# method that returns IEnumerable<string>.
  • Loop through a collection of names.
  • Use yield return to return only valid names.
  • Skip null, empty, or too-short names.
  • Print the filtered names with foreach.
Take Quiz

Keep learning

Related questions

View more