Design Patterns in life and Ruby — gain an intuitive understanding of OO design patterns by linking them with real-life examples.
The Iterator Pattern answers this question: What’s next?
The Iterator Pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
In English, an iterator returns items from a collection one at a time until it has returned all items from the collection.
Let’s use the Iterator Pattern to build our movie collection.

Let's pretend we have subscriptions for both Netflix and Amazon Prime. Our goal is to combine all the movies on Netflix and Amazon Prime to build our movie collection.
We can ask Netflix for a list of available movies. For simplicity sake, only three movies are listed.
netflix_movies = [
{
title: 'Train to Busan',
year: '2016',
director: 'Yeon Sang-ho',
genre: 'Drama film/Disaster Film',
length: '1h 58m'
},
{
title: 'The Big Short',
year: '2015',
director: 'Adam McKay',
genre: 'Drama film/Comedy-drama',
length: '2h 10m'
},
{
title: 'Zootopia',
year: '2016',
director: 'Byron Howard, Rich Moore, Jared Bush',
genre: 'Mystery/Crime film',
length: '1h 50m'
}
]
We can also ask Amazon for a list of available movies.
amazon_movies = {
0 => {
title: 'Mission: Impossible - Rogue Nation',
year: '2015',
director: 'Christopher McQuarrie',
genre: 'Thriller/Actio',
length: '2h 11m'
},
1 => {
title: 'The Hunger Games: Mockingja',
year: '2014',
director: 'Francis Lawrence',
genre: 'Fantasy/Science fiction film',
length: '2h 3m'
},
2 => {
title: 'Room',
year: '2015',
director: 'Lenny Abrahamson',
genre: 'Drama film/Thriller',
length: '1h 58m'
}
}
With both lists, we are ready to build our movie collection combining the two.
class MovieCollection
attr_reader :netflix_movies, :amazon_movies
def initialize(netflix_movies, amazon_movies)
@netflix_movies = netflix_movies
@amazon_movies = amazon_movies
@all_movies = get_all_movies
end
private
def get_all_movies
movies = []
netflix_movies.each do |movie|
movies << movie
end
amazon_movies.each do |movie_id, movie|
movies << movie
end
movies
end
end
Notice the way we loop through netflix_movies is different than the way we loop through amazon_movies. That is because netflix_movies is an array and amazon_movies is a hashmap from movie_id to movie.
In order to loop through netflix_movies and amazon_movies correctly, MovieCollection has to know netflix_movies is an array and amazon_movies is a hashmap.
This design is fine when there are only two movie subscriptions.
But now we want to subscribe to three more online movie subscriptions: Hulu, YouTube, and HBO. Their movie collection types are: array, array, and hashmap, respectively.
Including these three new subscriptions, our MovieCollection will look like this:
class MovieCollection
attr_reader :netflix_movies, :amazon_movies,
:hulu_movies, :youtube_movies, :hbo_movies
attr_accessor :all_movies
def initialize(netflix_movies, amazon_movies,
hulu_movies, youtube_movies, hbo_movies)
@netflix_movies = netflix_movies
@amazon_movies = amazon_movies
@hulu_movies = hulu_movies
@youtube_movies = youtube_movies
@hbo_movies = hbo_movies
@all_movies = get_all_movies
end
private
def get_all_movies
movies = []
netflix_movies.each do |movie|
movies << movie
end
amazon_movies.each do |movie_id, movie|
movies << movie
end
hulu_movies.each do |movie|
movies << movie
end
youtube_movies.each do |movie|
movies << movie
end
hbo_movies.each do |movie_id, movie|
movies << movie
end
movies
end
end
The get_all_movies method is fragile because it has to remember collection types for all movie vendors. A developer might accidentally change amazon_movies.each do |movie_id, movie| to amazon_movies.each do |movie|, and then the code is broken.
The method is also unscalable. As the list of subscriptions grows, it becomes less maintainable.
Time to use Iterators
The complexity in the example comes from the fact that different movie vendors use different collection types for their movies. Some use arrays, others use hashmaps, and the get_all_movies metod has to keep track of who uses what.
It would be great if the get_all_movies method could ignore collection types.
After all, all it cares about is getting and adding the next movie to movies until there are no more movies left in the current list.
Something like: while movie_iterator.has_next? { all_movies << movie_iterator.next } is what we want.
With this in mind, we can rewrite the method to:
def get_all_movies
movies = []
while netflix_movie_iterator.has_next?
movies << netflix_movie_iterator.next
end
while amazon_movie_iterator.has_next?
movies << amazon_movie_iterator.next
end
while hulu_movie_iterator.has_next?
movies << hulu_movie_iterator.next
end
while youtube_movie_iterator.has_next?
movies << youtube_movie_iterator.next
end
while hbo_movie_iterator.has_next?
movies << hbo_movie_iterator.next
end
movies
end
Obviously, it can be further simplified to:
def get_all_movies
movies = []
movie_iterators = [
netflix_movie_iterator,
amazon_movie_iterator,
hulu_movie_iterator,
youtube_movie_iterator,
hbo_movie_iterator
]
movie_iterators.each do |movie_iterator|
while movie_iterator.has_next?
movies << movie_iterator.next
end
end
movies
end
We can also pass the list of movie_iterators into initialize, so we can change our movie subscriptions without having to update the MovieCollection class.
class MovieCollection
attr_reader :movie_iterators
attr_accessor :all_movies
def initialize(movie_iterators)
@movie_iterators = movie_iterators
@all_movies = get_all_movies
end
private
def get_all_movies
movies = []
movie_iterators.each do |movie_iterator|
while movie_iterator.has_next?
movies << movie_iterator.next
end
end
movies
end
end
Time to create Iterators
The only thing left is to create movie_iterators.
Since there are two types of collections, array and hashmap, we need two types of iterators.
An ArrayIterator:
class ArrayIterator
attr_reader :collection
attr_accessor :current_index
def initialize(collection)
@collection = collection
@current_index = 0
end
def has_next?
current_index < collection.length
end
def next
raise 'No more item left.' unless has_next?
item = collection[current_index]
self.current_index += 1
item
end
end
And a HashIterator:
class HashIterator
attr_reader :collection, :keys
attr_accessor :current_index
def initialize(collection)
@collection = collection
@keys = collection.keys
@current_index = 0
end
def has_next?
current_index < keys.length
end
def next
raise 'No more item left.' unless has_next?
item = collection[keys[current_index]]
self.current_index += 1
item
end
end
And we can use them like this:

The most important thing is that both ArrayIterator and HashIterator use the same interface:
- an iterator is created by passing a collection into
initialize - an iterator responds to
has_next?andnext.
The unified iterator interface allows clients who use iterators, get_all_movies in our case, to be ignorant about the type of the collection. Clients can iterate through a collection with has_next? and next without knowing the structure of the collection.
Woohoo, that’s exactly the definition of the Iterator Pattern!
Takeaways
The Iterator Pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
With our movie collection, we are ready to binge watch ? ? ?.
Next time we will talk about group chats.
Don’t forget to subscribe so you won’t miss the next post! ?

Enjoyed the article?
My best content on Software Design, Rails, and Career in Dev. Delivered weekly.
Pingback: Jamika Formosa
Thanks, Jamika! I use SiteGround. Here is the link: https://www.siteground.com/index.htm?afcode=75e2460833918298faa054a974277bf9 🙂