Iterator
Intent
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Problem
A library system manages a collection of books stored internally as an array. Client code should be able to traverse the collection without depending on the array implementation, and multiple traversal strategies may be needed in the future.
Real-World Analogy
Think of a playlist on your music app. You press “next” to hear the next song without knowing how songs are stored — it could be an array, a linked list, or streamed from the cloud. The playlist gives you a simple way to go through songs one by one. You can also have different iterators: shuffle mode, repeat mode, or sorted by artist — all over the same collection.
When You Need It
- You’re building a social media feed that loads posts lazily — the iterator fetches the next batch from the server as the user scrolls, hiding the pagination logic
- You have a complex data structure (tree, graph, or composite) and need to walk through its elements without exposing the internal structure
- You want to provide multiple ways to traverse a collection — alphabetical, by date, by priority — without changing the collection itself
UML Class Diagram
classDiagram
class Iterator {
<<interface>>
+HasNext() bool
+Next() Book
}
class BookIterator {
-Book[] books
-int position
+HasNext() bool
+Next() Book
}
class Aggregate {
<<interface>>
+CreateIterator() Iterator
}
class BookCollection {
-List~Book~ books
+Add(Book) void
+CreateIterator() Iterator
}
class Book {
+Title string
}
Iterator <|.. BookIterator
Aggregate <|.. BookCollection
BookCollection --> BookIterator : creates
BookIterator --> Book
BookCollection o-- Book
Sequence Diagram
sequenceDiagram
participant Client
participant Aggregate
participant Iterator
Client->>Aggregate: createIterator()
Aggregate-->>Client: iterator
loop hasNext
Client->>Iterator: next()
Iterator-->>Client: element
end
Participants
| Participant | Role |
|---|---|
| Iterator | Defines the interface for accessing and traversing elements. |
| BookIterator | Implements the Iterator interface, tracking the current position. |
| Aggregate | Defines the interface for creating an Iterator. |
| BookCollection | Stores books and returns a BookIterator. |
| Book | The element being iterated over. |
How It Works
BookCollectionstores books and implementsCreateIterator()to return aBookIterator.- The
BookIteratormaintains a cursor position over the internal array. - Clients use
HasNext()andNext()to traverse without knowing the storage details.
Applicability
- You need to access an aggregate object’s contents without exposing its internal representation.
- You want to support multiple simultaneous traversals of the same aggregate.
- You want a uniform interface for traversing different aggregate structures.
Trade-offs
Pros:
- Provides a uniform way to traverse different collection types
- Hides the internal structure of the collection from client code
- Multiple iterators can traverse the same collection simultaneously
Cons:
- Less efficient than direct access for simple arrays or lists
- Adding a custom iterator to a new collection requires implementing the full interface
- External iterators can become invalid if the collection is modified during traversal
Example Code
C#
public class Book
{
public string Title { get; }
public Book(string title) => Title = title;
}
public interface IIterator
{
bool HasNext();
Book Next();
}
public interface IAggregate
{
IIterator CreateIterator();
}
public class BookIterator : IIterator
{
private readonly List<Book> _books;
private int _position;
public BookIterator(List<Book> books) => _books = books;
public bool HasNext() => _position < _books.Count;
public Book Next() => _books[_position++];
}
public class BookCollection : IAggregate
{
private readonly List<Book> _books = new();
public void Add(Book book) => _books.Add(book);
public IIterator CreateIterator() => new BookIterator(_books);
}
// Usage
var collection = new BookCollection();
collection.Add(new Book("Design Patterns"));
collection.Add(new Book("Clean Code"));
var iterator = collection.CreateIterator();
while (iterator.HasNext())
Console.WriteLine(iterator.Next().Title);
Delphi
type
TBook = class
public
Title: string;
constructor Create(const ATitle: string);
end;
IBookIterator = interface
function HasNext: Boolean;
function Next: TBook;
end;
TBookCollection = class;
TBookIterator = class(TInterfacedObject, IBookIterator)
private
FBooks: TList<TBook>;
FPosition: Integer;
public
constructor Create(ABooks: TList<TBook>);
function HasNext: Boolean;
function Next: TBook;
end;
TBookCollection = class
private
FBooks: TList<TBook>;
public
constructor Create;
destructor Destroy; override;
procedure Add(ABook: TBook);
function CreateIterator: IBookIterator;
end;
constructor TBook.Create(const ATitle: string);
begin
Title := ATitle;
end;
constructor TBookIterator.Create(ABooks: TList<TBook>);
begin
FBooks := ABooks;
FPosition := 0;
end;
function TBookIterator.HasNext: Boolean;
begin
Result := FPosition < FBooks.Count;
end;
function TBookIterator.Next: TBook;
begin
Result := FBooks[FPosition];
Inc(FPosition);
end;
constructor TBookCollection.Create;
begin
FBooks := TList<TBook>.Create;
end;
destructor TBookCollection.Destroy;
begin
FBooks.Free;
inherited;
end;
procedure TBookCollection.Add(ABook: TBook);
begin
FBooks.Add(ABook);
end;
function TBookCollection.CreateIterator: IBookIterator;
begin
Result := TBookIterator.Create(FBooks);
end;
// Usage
var
Collection: TBookCollection;
Iter: IBookIterator;
begin
Collection := TBookCollection.Create;
Collection.Add(TBook.Create('Design Patterns'));
Collection.Add(TBook.Create('Clean Code'));
Iter := Collection.CreateIterator;
while Iter.HasNext do
WriteLn(Iter.Next.Title);
end;
C++
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct Book {
std::string title;
};
class BookIterator {
const std::vector<Book>& books_;
size_t pos_ = 0;
public:
explicit BookIterator(const std::vector<Book>& books) : books_(books) {}
bool HasNext() const { return pos_ < books_.size(); }
const Book& Next() { return books_[pos_++]; }
};
class BookCollection {
std::vector<Book> books_;
public:
void Add(const Book& book) { books_.push_back(book); }
std::unique_ptr<BookIterator> CreateIterator() const {
return std::make_unique<BookIterator>(books_);
}
};
int main() {
BookCollection collection;
collection.Add({"Design Patterns"});
collection.Add({"Clean Code"});
auto it = collection.CreateIterator();
while (it->HasNext())
std::cout << it->Next().title << "\n";
return 0;
}
Runnable Examples
| Language | Source |
|---|---|
| C# | Iterator.cs |
| C++ | iterator.cpp |
| Delphi | iterator.pas |
Related Patterns
- Composite — Iterators are often used to traverse Composite structures.
- Factory Method — Polymorphic iterators use Factory Method to instantiate the appropriate iterator subclass.
- Memento — An iterator can use a Memento to capture the state of an iteration and roll back if needed.