Manikandan — Manikandan
Updated on 3 min read Manikandan Design Patterns

Iterator Design Pattern in .NET Core API

A practical Iterator pattern guide in C# with Internal, External, and Fail-fast iterator variants.

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

PartPurposeExample in this article
Iterator interfaceDefines traversal operationsIInvoiceIterator
Concrete iteratorImplements iteration state and movementInvoiceIterator
Aggregate/collectionCreates iterators over itemsInvoiceCollection
ClientConsumes iterator contractAPI/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

VariationControlBest use case
Internal IteratorCollection controls sequenceSimple pipeline-style traversal
External IteratorCaller controls pointerComplex traversal with pause/resume
Fail-fast IteratorDetects concurrent modificationGuarding 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 --> InvoiceIterator

A 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.

Share:
Back to Blog

Related Posts

View All Posts »