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:
-
Iterator – defines traversal methods
-
ConcreteIterator – actually implements the traversal
-
Aggregate – the collection interface
-
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