Iterator Design Pattern in .NET Core API
A practical Iterator pattern guide in C# with Internal, External, and Fail-fast iterator variants.

Intro
Collections in business systems often hide internal storage details, but callers still need controlled traversal. The Iterator pattern standardizes traversal without exposing collection internals.
What Is the Iterator Pattern?
Iterator is a behavioral design pattern that provides a way to access elements of a collection sequentially without revealing the underlying representation.
In C#, IEnumerable<T> and IEnumerator<T> are idiomatic iterator abstractions.
Iterator Class Example
public sealed record Invoice(Guid Id, decimal Total);
public interface IInvoiceIterator{ bool MoveNext(); Invoice Current { get; }}
public sealed class InvoiceCollection{ private readonly List<Invoice> _items = new();
public void Add(Invoice item) => _items.Add(item);
public IInvoiceIterator CreateIterator() => new InvoiceIterator(_items);}
public sealed class InvoiceIterator(List<Invoice> items) : IInvoiceIterator{ private int _index = -1;
public Invoice Current => items[_index];
public bool MoveNext() { if (_index + 1 >= items.Count) return false; _index++; return true; }}Why This Works
- traversal is isolated from collection internals
- multiple traversal strategies can coexist
- callers rely on stable iteration contracts
When to Use It
- exposing read-only traversal over custom collections
- providing paging/streaming style access
- supporting different traversal orders or filters
- preserving encapsulation of storage details
If direct indexing is enough, a full iterator abstraction may be unnecessary.
Important Note for ASP.NET Core
In ASP.NET Core, iterators are useful in data streaming endpoints and background processing loops. yield return can provide lazy execution and reduce memory pressure for large result sets.
Core Components of the Pattern
| Part | Purpose | Example in this article |
|---|---|---|
| Iterator interface | Defines traversal operations | IInvoiceIterator |
| Concrete iterator | Implements iteration state and movement | InvoiceIterator |
| Aggregate/collection | Creates iterators over items | InvoiceCollection |
| Client | Consumes iterator contract | API/service code |
Common Iterator Variations
Internal Iterator
The collection controls traversal and invokes a callback.
public void ForEach(Action<Invoice> action){ foreach (var item in _items) action(item);}External Iterator
The caller controls traversal explicitly through iterator state.
var iterator = collection.CreateIterator();while (iterator.MoveNext()) Console.WriteLine(iterator.Current.Total);Fail-fast Iterator
The iterator detects collection modifications during traversal and throws.
// Conceptual behavior similar to many fail-fast iterators:// throw new InvalidOperationException("Collection modified during iteration.");How the Variations Differ
| Variation | Control | Best use case |
|---|---|---|
| Internal Iterator | Collection controls sequence | Simple pipeline-style traversal |
| External Iterator | Caller controls pointer | Complex traversal with pause/resume |
| Fail-fast Iterator | Detects concurrent modification | Guarding iterator correctness in mutable collections |
SOLID Principles Behind It
SRP - Single Responsibility Principle
Collections manage data; iterators manage traversal.
OCP - Open/Closed Principle
Add new traversal strategies without modifying collection clients.
DIP - Dependency Inversion Principle
Clients depend on iterator abstractions rather than concrete storage.
Advantages and Disadvantages
Advantages
- hides internal data representation
- enables custom traversal behavior
- improves testability of collection consumers
Disadvantages (and Optional Alternatives)
- custom iterators can add extra classes Optional pattern: Facade (if traversal logic can be simplified behind one API).
- mutable collections may require fail-fast checks Optional pattern: Immutable collection approach (reduce concurrent mutation issues).
UML Diagram
classDiagram class Client class IInvoiceIterator { <<interface>> +MoveNext() +Current } class InvoiceIterator class InvoiceCollection { +CreateIterator() }
Client --> IInvoiceIterator IInvoiceIterator <|.. InvoiceIterator InvoiceCollection --> InvoiceIteratorA Quick Usage Example
var invoices = new InvoiceCollection();invoices.Add(new Invoice(Guid.NewGuid(), 120));invoices.Add(new Invoice(Guid.NewGuid(), 340));
var it = invoices.CreateIterator();while (it.MoveNext()) Console.WriteLine(it.Current.Total);How to Validate the Pattern
- iterator should work without leaking collection internals
- traversal order should be deterministic and test-covered
- external iterator should preserve state across calls
- fail-fast behavior should be validated under mutation scenarios
- new iterator styles should be pluggable without caller rewrites
Summary
The Iterator pattern gives .NET applications a clean and consistent way to traverse collections. Internal, External, and Fail-fast variants let you balance simplicity, control, and safety for different workflow needs.




