Skip to content

Iterator Pattern: Sequential Access Without Exposure

Walk a collection without exposing its internals

Builds on Collections: which one to pick

Stage 3core2 min read

The Iterator Pattern Solution

The new code uses the Iterator Pattern, a behavioral design pattern. It lets you visit the elements of a collection without showing how they are stored (list, stack, tree, etc.).


1. How This Solves the Problem

Now the code that walks through the elements is separate from the structure that stores them. Here is what that fixes:

ProblemHow Iterator Pattern Solves It
Direct access to internal dataThe collection no longer exposes its internal list or array. An iterator gives access to elements one by one, so the structure stays private.
No standard way to iterateAll traversal uses the same two methods (hasNext() / next()), however the data is stored.
Logic spread across client codeThe iterator class keeps track of the position (index). Client code stays short.
Difficult to customize traversalYou can write a new iterator class for each way of walking (reverse, filtering, shuffle). The collection does not change.
Tight coupling to collection typeClient code does not depend on the exact type (ArrayList vs. LinkedList). It uses only the interface, so you can change the type freely.

In the code, createIterator() gives the iterator the playlist's own list. The iterator reads it by position and the client never sees it. next() returns null when no elements are left. Java's own Iterator throws NoSuchElementException instead.


2. Key Improvements

  • Encapsulation: YouTubePlaylist no longer leaks its internal List<Video>.
  • Abstraction: The client does not know about the indices or the data structure.
  • Uniformity: The Playlist interface lets you add other collections (e.g., MusicPlaylist) that are walked in exactly the same way.
  • SOLID Compliance: It follows the Single Responsibility Principle, because storing data and walking through it are separate jobs.

3. Ideal Scenarios for Using the Iterator Pattern

The Iterator Pattern fits these situations:

  1. Hiding Complex Internals: Use it to walk through a collection (like a Tree or Graph) without revealing whether it is an ArrayList, Vector, or a custom structure.
  2. Multiple Traversal Variations: Use it when you need forward, reverse, or "Shuffle Play" order. Each can be its own iterator class.
  3. Unified Access: Use it to walk through different collections (videos, songs, or documents) with the same while loop.
  4. Decoupling Logic: Use it to keep how elements are stored apart from how they are read, so a change in one does not break the other.

4. Summary of the "Remote Control"

Think of the Iterator as a Remote Control and the Playlist as the Television. You do not need to know how the wires work inside the TV. You press "Next" on the remote. If you buy a new TV and the remote works the same way, you learn nothing new.

In this topic

After code1 file

After/Main.java104 linesGitHub
package DesignPatterns.BehaviouralPatterns.IteratorPattern.After;import java.util.ArrayList;import java.util.List;// A simple Video class with titleclass Video {    String title;    public Video(String title) {        this.title = title;    }    public String getTitle() {        return title;    }}/** * 1. THE AGGREGATE INTERFACE * This acts as a contract. Any collection that wants to be iterable * must be able to "create" an iterator for itself. */interface Playlist {    PlaylistIterator createIterator();}/** * 2. CONCRETE AGGREGATE * Stores the actual data. Notice it no longer has a 'getVideos()' method. * Encapsulation is now perfectly preserved. */class YouTubePlaylist implements Playlist {    // Internal data is now strictly private and hidden from the client    private List<Video> videos = new ArrayList<>();    public void addVideo(Video video) {        videos.add(video);    }    @Override    public PlaylistIterator createIterator() {        // We pass the internal list to a new iterator instance        return new YouTubePlaylistIterator(videos);    }}/** * 3. THE ITERATOR INTERFACE * Defines the standard operations for traversing ANY collection * without knowing how that collection is stored. */interface PlaylistIterator {    boolean hasNext(); // Is there another element?    Video next(); // Give me the next element and move the pointer.}/** * 4. CONCRETE ITERATOR * This class carries the "State" of the traversal (the 'position' variable). * This allows multiple clients to iterate over the SAME playlist simultaneously * because each has its own Iterator instance with its own 'position'. */class YouTubePlaylistIterator implements PlaylistIterator {    private List<Video> videos;    private int position;    public YouTubePlaylistIterator(List<Video> videos) {        this.videos = videos;        this.position = 0;    }    @Override    public boolean hasNext() {        return position < videos.size();    }    @Override    public Video next() {        // Standard safety check before returning data        return hasNext() ? videos.get(position++) : null;    }}public class Main {    public static void main(String[] args) {        YouTubePlaylist playlist = new YouTubePlaylist();        playlist.addVideo(new Video("LLD Tutorial"));        playlist.addVideo(new Video("System Design Basics"));        /**         * CLIENT CODE REVOLUTION:         * 1. The client doesn't know if 'playlist' uses a List, Array, or Map.         * 2. The client doesn't manage indices (i=0; i < size).         * 3. The client cannot accidentally call .clear() on the internal list.         */        PlaylistIterator iterator = playlist.createIterator();        while (iterator.hasNext()) {            System.out.println(iterator.next().getTitle());        }    }}

Done with this topic?

How well do you know it?

PreviousInverted Control & Structural Governance