RO EN

Iterator Pattern – traversing collections without exposing internals

Iterator Pattern – traversing collections without exposing internals
Doru Bulubasa
19 December 2025

In application development, there is often a need to traverse collections of objects: lists, trees, aggregates, or custom structures. Often, the internal implementation of these collections should not be exposed externally. Iterator Pattern solves exactly this problem, providing a standard method of traversal without revealing internal details.

Iterator Pattern is a behavioral design pattern, very commonly encountered in .NET, even if it is not always recognized as such.


What is Iterator Pattern?

Iterator Pattern provides an interface that allows sequential access to the elements of a collection without exposing its internal structure.

The basic idea is simple:

“Separates traversal logic from the collection structure.”

Thus, the client does not need to know whether it is traversing a list, an array, a tree, or a custom structure.


Why is it important?

Without Iterator Pattern:

  • Client code ends up depending on the collection implementation

  • Traversal of different structures becomes inconsistent

  • Any internal change breaks existing code

With Iterator Pattern:

  • The collection maintains its encapsulation

  • Traversal becomes uniform

  • Code is cleaner and easier to test


Pattern structure

Iterator Pattern includes the following components:

  1. Iterator – defines traversal methods

  2. ConcreteIterator – actually implements the traversal

  3. Aggregate – the collection interface

  4. ConcreteAggregate – the collection implementation

In .NET, these concepts are already well represented by IEnumerable and IEnumerator.


Iterator Pattern in .NET – the classic example

The best-known example of Iterator Pattern in .NET is:

foreach (var item in collection)
{
    // use item
}

Behind the scenes:

  • collection implements IEnumerable

  • foreach uses IEnumerator

  • the internal structure remains hidden


Practical example – custom iterator in C#

1️⃣ The collection

public class ProductCollection
{
    private readonly List<string> _products = new();

    public void Add(string product)
    {
        _products.Add(product);
    }

    public IEnumerator<string> GetEnumerator()
    {
        return _products.GetEnumerator();
    }
}

This collection:

  • hides the internal list

  • exposes only the traversal mechanism


2️⃣ Using the iterator

var products = new ProductCollection();
products.Add("Laptop");
products.Add("Mouse");
products.Add("Keyboard");

foreach (var product in products)
{
    Console.WriteLine(product);
}

✔ Client code knows nothing about List<string>

✔ Traversal is safe and controlled


Iterator with yield return

C# greatly simplifies iterator implementation through yield return:

public IEnumerable<int> GetEvenNumbers(int max)
{
    for (int i = 0; i <= max; i++)
    {
        if (i % 2 == 0)
            yield return i;
    }
}

This code:

  • automatically creates an iterator

  • is lazy (executes on demand)

  • consumes less memory


When is Iterator Pattern useful?

✔ when you have complex collections

✔ when you want to avoid exposing internals

✔ when you have multiple traversal modes

✔ when you want lazy iteration

✔ when you want easily testable code


Advantages

✅ Clear separation of responsibilities

✅ Better encapsulation

✅ Simple client code

✅ Native support in .NET

✅ Lazy evaluation


Disadvantages

⚠️ Overhead for very simple collections

⚠️ Increased complexity for trivial structures

In practice, the benefits almost always outweigh the disadvantages.


Iterator Pattern vs LINQ

LINQ heavily uses Iterator Pattern:

  • Where

  • Select

  • OrderBy

  • Take

All are implemented as lazy iterators, which explains their performance and flexibility.


Conclusion

Iterator Pattern is one of the most important and widely used design patterns in .NET. Even if you don't implement it manually every day, you constantly use it through foreach, IEnumerable, and LINQ.

Understanding this pattern helps you:

  • write cleaner code

  • avoid unnecessary dependencies

  • create extensible and robust collections