Iterator Pattern
Traverse a collection without exposing its representation, using a separate object for traversal state.
· Last updated: 2026-10-25 · 5 min read
Overview
Callers that index directly into a playlist array depend on its storage representation. Separating the stored items from the current traversal position lets the collection hide those details.
An Iterator advances to the next item and exposes the current one. The collection holds items; each iterator has its own position.
UML
Playlist creates an iterator. Callers use IEnumerator<string>.MoveNext() and Current. The C# compiler turns yield return into the iterator implementation.
History
GoF cataloged Iterator as a behavioral pattern in Design Patterns in 1994. It provides sequential access without exposing the aggregate representation.1
Why use it?
Callers read items in order without receiving the internal array. Independent iterators can traverse the same collection at different positions.
Deferred traversal may perform I/O or fail during enumeration. This example reads a copied array of strings, not a changing collection or a remote stream.
Example
Enumerate Playlist with foreach without exposing its array.
using System;
using System.Collections.Generic;
using System.Linq;
var playlist = new Playlist(new[] { "intro", "outro" });
foreach (string track in playlist) Console.WriteLine(track);
// Hide the internal array and expose only the traversal protocol.
sealed class Playlist : IEnumerable<string>
{
private readonly string[] tracks;
public Playlist(IEnumerable<string> tracks) => this.tracks = tracks.ToArray();
// Each call returns a generator with its own traversal position.
public IEnumerator<string> GetEnumerator()
{
foreach (string track in tracks) yield return track;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
}
intro
outroforeach uses the iterator protocol. C# yield return provides a concise way to write an implementation that supplies items one at a time.
An extended example
Create two iterators for the same playlist and advance them independently.
using System;
using System.Collections.Generic;
using System.Linq;
var playlist = new Playlist(new[] { "intro", "outro" });
using var first = playlist.GetEnumerator();
using var second = playlist.GetEnumerator();
if (first.MoveNext()) Console.WriteLine("first: " + first.Current);
if (second.MoveNext()) Console.WriteLine("second: " + second.Current);
if (first.MoveNext()) Console.WriteLine("first: " + first.Current);
Console.WriteLine("first has next: " + first.MoveNext());
Console.WriteLine("second: " + second.Current);
// Hide the internal array and expose only the traversal protocol.
sealed class Playlist : IEnumerable<string>
{
private readonly string[] tracks;
public Playlist(IEnumerable<string> tracks) => this.tracks = tracks.ToArray();
// Each call returns a generator with its own traversal position.
public IEnumerator<string> GetEnumerator()
{
foreach (string track in tracks) yield return track;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
}
first: intro
second: intro
first: outro
first has next: False
second: introAdvancing the first iterator does not change the second Current value. Do not read Current after MoveNext() returns false.
TypeScript example
Playlist owns the items; each iterator owns its current position. Keeping position out of the collection lets two traversals progress independently.
// Hide the internal array and expose only the traversal protocol.
class Playlist implements Iterable<string> {
private readonly tracks: readonly string[];
constructor(tracks: readonly string[]) { this.tracks = [...tracks]; }
// Each call returns a generator with its own traversal position.
*[Symbol.iterator](): Generator<string, void, unknown> {
for (const track of this.tracks) yield track;
}
}
function step(iterator: Iterator<string>): string {
const result = iterator.next();
return result.done ? "(end)" : result.value;
}
const playlist = new Playlist(["intro", "outro"]);
const first = playlist[Symbol.iterator]();
const second = playlist[Symbol.iterator]();
console.log("first: " + step(first));
console.log("first: " + step(first));
console.log("second: " + step(second));
console.log("second: " + step(second));first: intro
first: outro
second: intro
second: outroSymbol.iterator provides the entry point corresponding to C# GetEnumerator(). Each generator call creates a separate iterator; after traversal, check the done flag returned by next().
Related reading
Each C# example is a separate .NET 10 console program.
Compile the TypeScript examples for ES2022 or later and run the emitted JavaScript with Node.js.
Footnotes
- Gamma, Helm, Johnson, Vlissides. Design Patterns (1994). Publisher record. Behavioral patterns, Iterator. ↩