DEV Community

Cover image for Iterator: The "Next" Button Pattern
Vignesh Athiappan
Vignesh Athiappan

Posted on

Iterator: The "Next" Button Pattern

Iterator: The "Next" Button Pattern

You flip through TV channels with a remote. Press "next" — channel 1. Next — channel 2. Next — channel 3.

You don't know or care how the TV stores channels internally. Array? Linked list? Database? Doesn't matter. You press next and get the next one. The remote gives you one simple way to walk through everything, one at a time, without exposing the internals.

That's the Iterator pattern: walk through a collection one item at a time, without knowing how it's stored inside.

The problem it solves

Different collections store data completely differently. An array uses an index, a linked list uses node pointers, a tree walks branches. Without Iterator, your loop has to know each collection's internal structure:

// array — must know it's index-based
for (int i = 0; i < array.Length; i++) { ... }

// linked list — completely different traversal
var node = list.Head;
while (node != null) { ...; node = node.Next; }
Enter fullscreen mode Exit fullscreen mode

Every collection type forces a different loop. Change the storage and you rewrite every loop. Iterator hides all that behind one uniform "give me the next item."

The C# reality: you already have this

In C#, Iterator is the foreach loop, and it works on arrays, lists, dictionaries, sets — anything:

foreach (var item in anyCollection)
    Console.WriteLine(item);
Enter fullscreen mode Exit fullscreen mode

That one line works everywhere because every collection provides an iterator under the hood. You never see the difference between an array and a linked list — foreach just asks each one for the next item until it's done. You've used this pattern thousands of times without naming it.

Making your own class iterable

Here's the useful part. Say you build a custom Playlist class and want foreach to work on it. The keyword yield return makes that effortless:

public class Playlist
{
    private readonly List<string> _songs = new();
    public void Add(string song) => _songs.Add(song);

    public IEnumerator<string> GetEnumerator()
    {
        foreach (var song in _songs)
            yield return song;      // hand out one at a time
    }
}
Enter fullscreen mode Exit fullscreen mode

Now your own class walks like any built-in collection:

var playlist = new Playlist();
playlist.Add("Song A");
playlist.Add("Song B");

foreach (var song in playlist)     // works — because we gave it an iterator
    Console.WriteLine(song);
Enter fullscreen mode Exit fullscreen mode

yield return: the magic word

yield return is genuinely powerful. It pauses the method, hands back one item, and remembers where it left off for the next request. That makes it lazy — items are produced only when asked for, not all at once.

Which unlocks something wild: infinite sequences.

public IEnumerable<int> Naturals()
{
    int n = 1;
    while (true)           // infinite loop, but safe
        yield return n++;  // produces one number only when asked
}

foreach (var num in Naturals().Take(5))
    Console.WriteLine(num);   // 1 2 3 4 5 — the rest never get computed
Enter fullscreen mode Exit fullscreen mode

An infinite loop that doesn't hang, because yield only computes the next value when you actually ask for it. Try that with a normal List and it runs out of memory instantly. This laziness is also why LINQ can chain .Where().Select().Take() efficiently — it's all iterators, computed on demand.

The two interfaces

  • IEnumerable means "I can be iterated" — the collection.
  • IEnumerator means "I do the actual walking" — the cursor, with MoveNext() and Current.

foreach calls GetEnumerator() to get a cursor, then calls MoveNext() until it's done. You almost never write these by hand — yield return generates them for you.

Where it's everywhere

  • Every foreach loop you've ever written.
  • All of LINQ — .Where(), .Select(), .Take() return lazy iterators.
  • Streaming large files or database results row by row via yield, instead of loading everything into memory.
  • Paginated APIs that yield results page by page.

One line to remember

Iterator = the "next" button. Walk a collection one item at a time without caring how it's stored — and in C#, foreach + yield return give it to you for free.


Part 12 of a series on must-know design patterns in C#, explained the simple way. Earlier parts covered Singleton, Factory Method, Builder, Adapter, Decorator, Facade, Proxy, Composite, Observer, Strategy, and Command. Next up: State — when an object changes its behavior as its state changes.

Top comments (0)