
Iterator Design Pattern: The Ultimate Guide for Software Architects Using Microsoft Technologies
- Sudhir mangla
- Behavioral design patterns
- 19 Apr, 2025
So, you’re here because you’ve heard whispers about this mysterious thing called the Iterator Pattern. Or maybe you’re a seasoned developer who’s looking for a comprehensive refresher filled with juicy details, practical examples, and a little humor. Whatever the reason, you’re in the right place.
By the end of this article, you’ll have mastered the Iterator Pattern with the confidence of a Jedi wielding a lightsaber—ready to tackle those pesky collections like a true software architect.
What Exactly is the Iterator Pattern?
Let’s kick things off simple. Imagine you’re flipping through your favorite playlist. You just hit “Next,” and the next song magically starts playing, right? You don’t care how your playlist is structured internally—arrays, linked lists, or hamsters running on wheels. All you want is that sweet next track.
That’s exactly what the Iterator Pattern is about.
The Iterator Pattern provides a simple way to access elements of a collection without exposing the underlying structure of the collection itself. It’s part of the Behavioral Design Patterns family because it focuses on the communication between objects—particularly about accessing elements in collections sequentially.
In simple English:
- It lets you traverse (move through) a collection of objects one by one.
- It keeps things neat by hiding the complexities of how the collection actually works behind the scenes.
- It’s all about simplicity and encapsulation—two things software architects love!
Principles of the Iterator Pattern
Time to peek under the hood. The Iterator Pattern follows some simple, yet powerful, principles:
1. Single Responsibility Principle (SRP)
- Iterators focus only on traversing collections.
- Collections focus only on managing their elements.
Analogy: Imagine you have a driver and a navigator. One drives; the other gives directions. Neither tries to do the other’s job. Everyone stays happy (and alive).
2. Open/Closed Principle (OCP)
- Easily add new ways to traverse collections without changing existing code.
Analogy: Think of your favorite streaming service. New categories (like “Action Sci-Fi Rom-Com”) pop up without changing your existing navigation method. You just keep clicking “Next”.
3. Encapsulation
- Hide internal data structure details from clients.
Analogy: You don’t need to know how Netflix organizes shows behind the scenes; you just keep clicking play and enjoy your binge.
When Should You Actually Use the Iterator Pattern?
Good question! Use the Iterator Pattern when:
- You want clean, reusable traversal logic:
- Avoid rewriting loops for different collections.
- Your collection structure changes frequently:
- Change your data structure without altering traversal code.
- You want to simplify the client code:
- Clients use collections seamlessly, no messy loops.
But avoid it when:
- Your collection is trivial, like an array with known size, and adding complexity doesn’t make sense.
Key Components: The Heroes of Our Story
Let’s meet the team behind the magic:
1. Iterator Interface
Defines methods for traversing collections.
interface IIterator<T>
{
T First();
T Next();
bool IsDone { get; }
T CurrentItem { get; }
}
2. Concrete Iterator
Implements the iterator interface, managing current traversal state.
class ConcreteIterator<T> : IIterator<T>
{
private ConcreteCollection<T> _collection;
private int _current = 0;
public ConcreteIterator(ConcreteCollection<T> collection)
{
_collection = collection;
}
public T First()
{
_current = 0;
return _collection[_current];
}
public T Next()
{
_current++;
return !IsDone ? _collection[_current] : default;
}
public T CurrentItem => _collection[_current];
public bool IsDone => _current >= _collection.Count;
}
3. Aggregate Interface
Defines the method to create iterators.
interface IAggregate<T>
{
IIterator<T> CreateIterator();
}
4. Concrete Aggregate
Stores elements and creates concrete iterators.
class ConcreteCollection<T> : IAggregate<T>
{
private List<T> _items = new List<T>();
public IIterator<T> CreateIterator()
{
return new ConcreteIterator<T>(this);
}
public int Count => _items.Count;
public T this[int index]
{
get => _items[index];
set => _items.Insert(index, value);
}
public void Add(T item)
{
_items.Add(item);
}
}
Detailed Implementation in C#
Step-by-step walkthrough
Let’s get practical. Imagine you’re building a playlist manager. Here’s exactly how you’d implement the Iterator Pattern in C#.
Step 1: Define the Iterator Interface
public interface IIterator<T>
{
T First();
T Next();
bool IsDone { get; }
T CurrentItem { get; }
}
Step 2: Define the Concrete Iterator
public class PlaylistIterator : IIterator<Song>
{
private Playlist _playlist;
private int _current = 0;
public PlaylistIterator(Playlist playlist)
{
_playlist = playlist;
}
public Song First()
{
_current = 0;
return _playlist[_current];
}
public Song Next()
{
_current++;
if (!IsDone)
return _playlist[_current];
else
return null;
}
public Song CurrentItem => _playlist[_current];
public bool IsDone => _current >= _playlist.Count;
}
Step 3: Define the Aggregate Interface
public interface IPlaylistAggregate
{
IIterator<Song> CreateIterator();
}
Step 4: Define the Concrete Aggregate
public class Playlist : IPlaylistAggregate
{
private List<Song> _songs = new List<Song>();
public IIterator<Song> CreateIterator()
{
return new PlaylistIterator(this);
}
public int Count => _songs.Count;
public Song this[int index]
{
get => _songs[index];
set => _songs.Insert(index, value);
}
public void Add(Song song)
{
_songs.Add(song);
}
}
public class Song
{
public string Title { get; set; }
public string Artist { get; set; }
public override string ToString() => $"{Title} by {Artist}";
}
Step 5: Using your Iterator
static void Main(string[] args)
{
var playlist = new Playlist();
playlist.Add(new Song { Title = "Imagine", Artist = "John Lennon" });
playlist.Add(new Song { Title = "Hotel California", Artist = "Eagles" });
playlist.Add(new Song { Title = "Hey Jude", Artist = "The Beatles" });
IIterator<Song> iterator = playlist.CreateIterator();
Console.WriteLine("Playlist:");
for (Song song = iterator.First(); !iterator.IsDone; song = iterator.Next())
{
Console.WriteLine(song);
}
}
Result
Playlist:
Imagine by John Lennon
Hotel California by Eagles
Hey Jude by The Beatles
🚀 Different Ways to Implement the Iterator Pattern in C#
Believe it or not, there’s more than one way to skin this design-pattern cat. The classic implementation (which we just did) is solid—but hey, C# gives us tools that can make our lives easier.
Let’s explore a few different flavors:
🔁 1. Using yield return
(C# Iterator Blocks)
This is the C# magic wand! If you just want to iterate and don’t need to implement a full iterator class, you can use yield return
.
public class MagicPlaylist
{
private List<Song> _songs = new List<Song>();
public void Add(Song song) => _songs.Add(song);
public IEnumerable<Song> GetSongs()
{
foreach (var song in _songs)
{
yield return song;
}
}
}
Usage:
var playlist = new MagicPlaylist();
playlist.Add(new Song { Title = "Lose Yourself", Artist = "Eminem" });
playlist.Add(new Song { Title = "Hallelujah", Artist = "Leonard Cohen" });
foreach (var song in playlist.GetSongs())
{
Console.WriteLine(song);
}
Why it’s awesome:
- Less boilerplate.
- No need for a separate iterator class.
- Clean and expressive.
🧱 2. Classic Iterator with Interfaces (Already Covered)
This is your go-to when you want full control over the iteration process, or when you’re dealing with multiple custom traversal strategies.
You’ve already seen this one—implementing IIterator<T>
and IAggregate<T>
manually.
🧩 3. Implementing IEnumerable<T>
and IEnumerator<T>
Want your custom class to play nice with foreach
, LINQ, and the whole C# ecosystem? Implement these .NET interfaces.
public class SmartPlaylist : IEnumerable<Song>
{
private List<Song> _songs = new List<Song>();
public void Add(Song song) => _songs.Add(song);
public IEnumerator<Song> GetEnumerator() => _songs.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Now you can do this:
var smartPlaylist = new SmartPlaylist();
smartPlaylist.Add(new Song { Title = "Fix You", Artist = "Coldplay" });
foreach (var song in smartPlaylist)
{
Console.WriteLine(song);
}
🧠 Use Cases: When Does the Iterator Pattern Shine?
Not sure where to use the Iterator Pattern in the wild? Let’s break it down:
✅ 1. Traversing Complex Collections
When you’re working with data structures like trees, graphs, or composite structures (hello, Composite Pattern), you don’t want to leak traversal logic all over the place. Iterator to the rescue!
✅ 2. Providing Multiple Traversal Strategies
Want to iterate forward, backward, or even skip every other item? Use different iterator implementations and let the client choose.
✅ 3. Providing Uniform Access
Maybe your application uses different kinds of collections—arrays, lists, even database rows. You can build adapters that expose a common iterator interface so client code doesn’t have to change.
✅ 4. Paging Large Datasets
When you’re dealing with paginated API results or database queries, iterators let you fetch one page at a time without overwhelming memory.
🛑 Anti-Patterns to Avoid
Even the mighty Iterator Pattern can be misused. Here are a few “facepalm” moments to dodge:
❌ 1. Mixing Iteration and Collection Logic
Keep your iterator and collection logic separate. The moment your collection starts knowing about traversal state, you’ve broken SRP and invited chaos.
❌ 2. Forcing Iterators on Simple Collections
If you’re only looping through a list once, don’t over-engineer. Just use a foreach
loop. Iterator Pattern is powerful, but not always necessary.
❌ 3. Exposing Internal State
If your iterator lets the client change internal elements or track indices directly… you’re defeating the purpose. The whole point is encapsulation.
❌ 4. Uncontrolled Mutation During Iteration
Mutating a collection while iterating over it? That’s the software version of stepping on a rake.
🌟 Advantages of the Iterator Pattern
Let’s count the wins:
✅ Decouples Traversal Logic
Clients don’t need to know how the collection is structured. They just iterate like champs.
✅ Promotes Clean Code
Iterator Pattern helps remove for
loop clutter from your codebase and makes traversal reusable.
✅ Multiple Iteration Strategies
Different iterators for different needs—linear, reverse, filtered—you name it.
✅ Easy to Plug into Frameworks
Implement IEnumerable<T>
and suddenly you’re best friends with foreach
, LINQ, async streams, and more.
⚠️ Disadvantages of the Iterator Pattern
Okay, it’s not all sunshine and rainbows. Some caveats:
⚠️ Extra Classes
You’ll often need more classes/interfaces, which can feel like overkill for simple collections.
⚠️ May Add Complexity
Especially if you implement custom iteration strategies when built-in iterators would do just fine.
⚠️ Performance Overhead
Depending on the implementation, wrapping or copying collections can add some runtime overhead—especially in memory-sensitive applications.
⚠️ Can Be Misapplied
Used where it isn’t needed, it bloats the code instead of simplifying it.
🧠 One More Time — Anti-Patterns to Avoid
Yes, this is in here twice. Why? Because it’s that important. Let’s turn it into a checklist:
- Don’t blend iteration with business logic.
- Avoid modifying the collection while iterating.
- Don’t overuse where a simple
foreach
will suffice. - Keep your encapsulation tight.
- Never leak traversal state to the client.
🧾 Conclusion: So, Why Should You Care?
The Iterator Pattern is like a remote control for your collections. You don’t need to know how the internals work—you just press “Next.”
For software architects working with Microsoft technologies, this pattern is pure gold. C# gives you all the tools you need—interfaces, yield return
, IEnumerable<T>
, and more—to build elegant, extensible traversal systems.
Remember:
- Use it when collections get complex or require flexible traversal.
- Avoid it when it adds more complexity than it solves.
- Love it for what it is: a clean, powerful way to walk through your data.
By now, you’re equipped to wield the Iterator Pattern like Thor’s hammer. Use it wisely, and may your collections always be iterable.