An Iterator is an object that traverses a collection without exposing its internal representation. It separates the movement through elements from the structure that stores them.
Imagine you are tourists in a huge city like Rome. You want to see all the tourist attractions.
You can do it in several ways:
- Randomly: Walk around randomly until you find something.
- Sequentially: Follow a step-by-step guide from a magazine.
- With a tour guide: Hire someone who knows the city and takes you from one place to another.
The iterator plays the role of tour guide for the journey.
In programming, we have many ways to store groups of objects: Arrays, Linked Lists, Binary Trees, Graphs, HashMaps… Each has a different internal structure.
- An Array is traversed with an index
i++. - A Linked List is traversed by jumping from node to node.
- A Tree is traversed in pre-order, in-order, or post-order.
The problem is that if our client code (the one using the collection) has to know how the collection is structured in order to traverse it, we are creating tight coupling. If tomorrow you change the list for a tree, you will have to rewrite all the for loops in your application.
The Problem: Access to elements
We want a way to access the elements of a container sequentially without exposing its internal representation.
We don’t want the client to do this:
// ❌ The client knows too much about the internal structure
for (int i = 0; i < collection.Length; i++) { ... }
// or
while (node != null) { node = node.Next; }We want them to do this:
// ✅ The client just asks for "the next one"
while (iterator.HasNext()) {
var element = iterator.Next();
}The Solution: The Iterator
The pattern suggests extracting the traversal logic from the collection and placing it in a separate object called an Iterator.
“Teaching” Implementation in C#
Although C# already has this solved (we’ll see it later), let’s implement it from scratch to understand the mechanics.
The Interfaces
// Iterator Interface
public interface IIterator<T>
{
T Next();
bool HasNext();
}
// Collection Interface (Aggregate)
public interface ICollection<T>
{
IIterator<T> CreateIterator();
}The Concrete Collection
Imagine a Spotify Playlist. Internally it uses a list, but it could use anything.
public class Playlist : ICollection<string>
{
private List<string> _songs = new List<string>();
public void AddSong(string song) => _songs.Add(song);
public int Count => _songs.Count;
public string Get(int index) => _songs[index];
// Iterator factory
public IIterator<string> CreateIterator()
{
return new PlaylistIterator(this);
}
}The Concrete Iterator
This is the object that knows how to move through the Playlist.
public class PlaylistIterator : IIterator<string>
{
private Playlist _playlist;
private int _currentPosition = 0;
public PlaylistIterator(Playlist playlist)
{
_playlist = playlist;
}
public bool HasNext()
{
return _currentPosition < _playlist.Count;
}
public string Next()
{
if (!HasNext()) return null;
string song = _playlist.Get(_currentPosition);
_currentPosition++;
return song;
}
}The Client
class Program
{
static void Main(string[] args)
{
var myMusic = new Playlist();
myMusic.AddSong("Bohemian Rhapsody");
myMusic.AddSong("Stairway to Heaven");
myMusic.AddSong("Hotel California");
// The client asks for an iterator.
// It does NOT know if internally there is an array or a linked list.
IIterator<string> iterator = myMusic.CreateIterator();
Console.WriteLine("--- Playing Playlist ---");
while (iterator.HasNext())
{
string song = iterator.Next();
Console.WriteLine($"🎵 Playing: {song}");
}
}
}Real Implementation in C# (IEnumerable and yield)
In .NET, the Iterator pattern is so integrated that the language has native support.
- The
Aggregateinterface isIEnumerable<T>. - The
Iteratorinterface isIEnumerator<T>.
And best of all: we don’t need to create the PlaylistIterator class manually. C# gives us the yield return keyword.
When we use yield return, the compiler automatically generates a class behind the scenes that implements the Iterator pattern (a state machine).
The Modern Example
// We implement IEnumerable so C# knows this can be traversed
public class ModernPlaylist : IEnumerable<string>
{
private List<string> _songs = new List<string>();
public void Add(string song) => _songs.Add(song);
// Iterator implementation
public IEnumerator<string> GetEnumerator()
{
// We could have complex logic here, like traversing in reverse
// or filtering explicit songs.
foreach (var s in _songs)
{
// yield return returns the element and "pauses" execution
// until the loop asks for the next one.
yield return s;
}
}
// Boilerplate required for compatibility with older versions
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}Usage with foreach
The foreach loop is simply syntactic sugar. The compiler translates it into while (iterator.MoveNext()) calls.
var playlist = new ModernPlaylist();
playlist.Add("Despacito"); // (Sorry)
playlist.Add("Gasolina");
// This uses the Iterator pattern underneath
foreach (var song in playlist)
{
Console.WriteLine(song);
}Why is this useful?
The real power of the Iterator is not just traversing lists. It’s abstracting complex traversals.
Imagine a Family Tree. You can create different iterators for the same collection:
GetAncestorsIterator(): Traverses upwards (parents, grandparents…).GetDescendantsIterator(): Traverses downwards.GetOnlyMaleChildrenIterator(): Traverses with filtering.
The client code is still a simple foreach, but the logic of “how I move through the tree” is encapsulated in each iterator.
Today, thanks to IEnumerable and yield in C#, or Generators in JavaScript/Python, implementing it is trivial, but understanding what happens behind the scenes helps us design more robust data structures.