The Iterator Design Pattern is a behavioral design pattern that provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Umm 🤔… What do you mean by an aggregate ??
By aggregate we mean a collection/container that stores a group of things (e.g., a list, array, tree, graph etc.)
So the pattern gives us a way to step through the elements of an aggregate without having to know the internal representation of things.
If none of this made sense😓… let’s jump into an example and see what this pattern is all about.
You would have heard about the the merger of Jio Cinema and Disney+ Hotstar.
When two large streaming platforms merge into a unified platform like JioHotstar, one of the technical challenges is combining their movie catalogs.
The challenge becomes interesting when each system stores data using different data structures.
Let us suppose Jio Cinema uses an ArrayList whereas Disney+ Hotstar uses an array data structure to store movie catalogs. In such a situation, our client code(JioHotstar) must understand both data structures resulting in tightly coupled and messy code.
Let’s say we have the below class representing movies 🎬.
class Movie {
String title;
public Movie(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
}According to the situation mentioned above, Jio Cinema uses an ArrayList,
class JioCinema {
// Uses a list to store movies
List<Movie> movies;
public JioCinema() {
movies = new ArrayList<>();
movies.add(new Movie("RRR"));
movies.add(new Movie("Dune"));
movies.add(new Movie("Fast X"));
}
// Returns the actual data structure(list) to client
// And leaves the responsibility of iteration to the client
public List<Movie> getMovies() {
return movies;
}
}whereas, Disney+ Hotstar uses an array.
class Hotstar {
// Uses an array to store movies
Movie[] movies;
public Hotstar() {
movies = new Movie[3];
movies[0] = new Movie("Avengers");
movies[1] = new Movie("Iron Man");
movies[2] = new Movie("Loki Special");
}
// Returns the actual data structure(array) to client
// And leaves the responsibility of iteration to the client
public Movie[] getMovies() {
return movies;
}
}Now JioHotstar displays the movie catalogs for both Jio Cinema and Disney+ Hotstar
class JioHotstarApp {
public static void main(String[] args) {
// Initialize Jio Cinema and Hotstar
JioCinema jio = new JioCinema();
Hotstar hotstar = new Hotstar();
// Get the movie catalogs for both the platforms
List<Movie> jioMovies = jio.getMovies();
Movie[] hotstarMovies = hotstar.getMovies();
// iterate JioCinema list
for (int i = 0; i < jioMovies.size(); i++) {
Movie movie = jioMovies.get(i);
System.out.println(movie.getTitle());
}
// iterate Hotstar array
for (int i = 0; i < hotstarMovies.length; i++) {
Movie movie = hotstarMovies[i];
if (movie != null) {
System.out.println(movie.getTitle());
}
}
}
}Now… what is the problem with the above code 🤔??
- The client has to know that Jio Cinema uses a list and hence it should be iterated like:
for (int i = 0; i < jioMovies.size(); i++) {
Movie movie = jioMovies.get(i);
System.out.println(movie.getTitle());
}
- Whereas Hotstar uses an array which should be iterated like:
for (int i = 0; i < hotstarMovies.length; i++) {
Movie movie = hotstarMovies[i];
if (movie != null) {
System.out.println(movie.getTitle());
}
}
Tomorrow if a new platform joins with another data structure, the client code must be modified again,
violating …You guessed it !!! The Open-Close Principle.
The Iterator Pattern
As mentioned earlier, the iterator pattern allows us to access elements of a collection without exposing its internal structure, hence the client simply asks for an Iterator and loops over it.
Let’s start by creating an interface for iterating over movies – MovieIterator.
interface MovieIterator {
// A boolean that tells us whether
// The aggregate has any more movies or not
boolean hasNext();
// Provides the next movie
Movie next();
}Now let us create concrete implementations of our MovieIterator for both the platforms:
class JioCinemaIterator implements MovieIterator {
List<Movie> movies;
int position = 0;
public JioCinemaIterator(List<Movie> movies) {
this.movies = movies;
}
public boolean hasNext() {
return position < movies.size();
}
public Movie next() {
return movies.get(position++);
}
}class HotstarIterator implements MovieIterator {
Movie[] movies;
int position = 0;
public HotstarIterator(Movie[] movies) {
this.movies = movies;
}
public boolean hasNext() {
return position < movies.length && movies[position] != null;
}
public Movie next() {
return movies[position++];
}
}Now that we have MovieIterator‘s for both the platforms, we can drop
public List<Movie> getMovies() {
return movies;
}
from JioCinema and instead add a way to call it’s iterator:
class JioCinema {
List<Movie> movies;
public JioCinema() {
movies = new ArrayList<>();
movies.add(new Movie("RRR"));
movies.add(new Movie("Dune"));
movies.add(new Movie("Fast X"));
}
/**
Instead of passing the entire data structure,
We now provide an iterator to the client.
This allows the client to access elements of a collection
without exposing its internal structure
*/
public MovieIterator createIterator() {
return new JioCinemaIterator(movies);
}
}Similarly, for Hotstar we do not need
public Movie[] getMovies() {
return movies;
}
Instead, we can add a way to access it’s MovieIterator
class Hotstar {
Movie[] movies;
public Hotstar() {
movies = new Movie[3];
movies[0] = new Movie("Avengers");
movies[1] = new Movie("Iron Man");
movies[2] = new Movie("Loki Special");
}
public MovieIterator createIterator() {
return new HotstarIterator(movies);
}
}Now our client can display all the movies using:
class JioHotstarApp {
public static void main(String[] args) {
JioCinema jio = new JioCinema();
Hotstar hotstar = new Hotstar();
printMovies(jio.createIterator());
printMovies(hotstar.createIterator());
}
// The client now has a uniform way to access all movies
// Irrespective of the data structure used by the individual platforms
static void printMovies(MovieIterator iterator) {
while(iterator.hasNext()) {
Movie movie = iterator.next();
System.out.println(movie.getTitle());
}
}
}And there you go🥁…your first implementation of iterator pattern, ready to merge your favorite streaming platforms😅.
Let’s now look at the UML diagram for Iterator Design pattern:

Now that you have understood the pattern, let’s see how easy it is to extend this design.
Suppose tomorrow the merger includes Netflix as well and Netflix uses a HashSet data structure to store movie catalogs.
In that case how do we add Netflix‘s movie catalog to JioHotstarApp(as it is our client for now) ??
Let’s start by implementing a concrete iterator for Netflix – NetflixIterator
class NetflixIterator implements MovieIterator {
Iterator<Movie> iterator;
public NetflixIterator(Set<Movie> movies) {
this.iterator = movies.iterator();
}
public boolean hasNext() {
return iterator.hasNext();
}
public Movie next() {
return iterator.next();
}
}Let’s suppose below is the class for Netflix movie catalogs:
class Netflix {
Set<Movie> movies;
public Netflix() {
movies = new HashSet<>();
movies.add(new Movie("Stranger Things"));
movies.add(new Movie("Money Heist"));
movies.add(new Movie("The Witcher"));
}
public MovieIterator createIterator() {
return new NetflixIterator(movies);
}
}Then we can simply add Netflix movies to our client.
class JioHotstarApp {
public static void main(String[] args) {
JioCinema jio = new JioCinema();
Hotstar hotstar = new Hotstar();
Netflix netflix = new Netflix();
printMovies(jio.createIterator());
printMovies(hotstar.createIterator());
printMovies(netflix.createIterator());
}
static void printMovies(MovieIterator iterator) {
while(iterator.hasNext()) {
Movie movie = iterator.next();
System.out.println(movie.getTitle());
}
}
}And that’s how easy it is to extend this design 😎.
Why not use the Java Iterator interface??
Now that you have a good idea💡 about the Iterator design pattern, you might be wondering why did we not us the Java Iterator interface.
We did it, so that you could build an iterator from scratch. But in most real-life situations, we are often going to use the Java Iterator interface, as it provides us a lot of leverage.
The Collection interface extends the Iterable interface which includes an iterator method that returns an iterator implementing the Iterator interface.
As a result, if we relied on Java’s built-in iterator mechanism in the above example, we would not need to create separate iterator implementations for collections such as ArrayList and HashSet.
However, when working with arrays or custom data structures where iteration requires non-trivial traversal such as Depth-First Traversal (DFT) or Breadth-First Traversal (BFT), you will still need to implement your own concrete iterators.
And with that, we’ve wrapped up the Iterator Design Pattern – you’re now fully equipped with everything you need to know on Iterators 🥳.