Skip to content

// Design Patterns · 16

Iterator

A collection to walkleads toOne cursor, hidden storage

Walk a collection without exposing how it is stored.

How it fits together

Iterator structure«client»SyncJobfor each customer«interface»Iterator<Customer>MoveNext()CurrentCustomerApiClientAll() → iteratorPagedIteratortoken, buffernext page of 200 on demand«external»Customer APIGET /customers?tokenAll()returnsMoveNext()next page
uses / callscreatesimplements
  • SyncJob uses / calls CustomerApiClient (All())
  • CustomerApiClient creates PagedIterator (returns)
  • SyncJob uses / calls Iterator<Customer> (MoveNext())
  • PagedIterator implements Iterator<Customer>
  • PagedIterator uses / calls Customer API (next page)

Spot it

  • Callers index into internal arrays or pages
  • Loading every row just to loop over them
  • Paging logic copied into every consumer

It costs

Lazy iteration hides I/O, and walking it twice can repeat the work.

In code

public sealed record CustomerPage(IReadOnlyList<Customer> Items, string? NextToken);​public sealed class CustomerApiClient(HttpClient http){    public async IAsyncEnumerable<Customer> AllAsync(        [EnumeratorCancellation] CancellationToken ct = default)    {        string? token = null;        do        {            var url = $"customers?limit=200&token={Uri.EscapeDataString(token ?? "")}";            var page = await http.GetFromJsonAsync<CustomerPage>(url, ct)                ?? throw new InvalidOperationException("Empty page");            foreach (var customer in page.Items)            {                yield return customer;            }            token = page.NextToken;        }        while (token is not null);    }}
Go deeperThe full explanation and what to ask for · 1 min readExpand

An iterator gives sequential access to the elements of an aggregate through a small interface, such as move-next and current, while the storage (an array, a tree, a paged API) stays private. Several iterators can walk the same collection independently.

Most languages build this in: a for-each loop over an iterable, and generator functions with yield that produce lazy sequences. Nothing runs until someone iterates, and iterating twice runs everything twice, so materialise the results once when you need them more than once. Do not modify a collection while iterating it.

Iterator is how you traverse a Composite without knowing its shape; Visitor is the alternative when each element type needs its own operation. Async iterators carry the idea over to paged and streamed I/O, where the consumer pulling items is a natural form of backpressure.

Ask for it

  • iterator
  • cursor
  • lazy sequence
  • generator / yield
  • aggregate
  • pagination

Consumers of CustomerApiClient fetch page after page by hand. Add an iterator that yields customers one at a time and requests the next page of 200 via the continuation token only when the caller reaches the end, so callers can stop early. Keep the token handling private.