The iterator design pattern gives us a way to go through the elements of a collection one at a time without knowing how the collection stores them. In Java, the pattern is part of the language. The interface java.util.Iterator is the iterator, and java.lang.Iterable is the collection that creates iterators, so every class that implements Iterable works in a for-each loop.
We write our own iterator when a class keeps its elements in a structure that callers should not see, such as an array with a moving start index or a tree. Callers loop over the elements, and the class keeps the freedom to change its storage later.
The following example adds four products to a “recently viewed” list that keeps only the last three, and reads them newest first through an iterator.
RecentlyViewed recent = new RecentlyViewed(3);
recent.add("apple");
recent.add("banana");
recent.add("cherry");
recent.add("mango"); // "apple" drops out (capacity 3)
Iterator<String> it = recent.iterator();
String newest = it.next(); // "mango"
String second = it.next(); // "cherry"
boolean more = it.hasNext(); // true
for (String item : recent) {
System.out.println(item); // mango, cherry, banana
}
The caller never touches an index or the array inside RecentlyViewed. It only calls hasNext() and next(), or lets the for-each loop call them.
We start with the roles of the pattern and the Java interfaces that play them, and build RecentlyViewed step by step. After that, we add a second traversal order and go through the rules that every Iterator must follow, including fail-fast behavior.
1. How the Iterator Pattern Works
The Gang of Four book describes the iterator pattern as a way to access the elements of an aggregate object sequentially without exposing its underlying representation. An aggregate is any object that holds a group of other objects. The pattern moves the job of walking through the elements out of the aggregate and into a separate iterator object, which remembers the current position.
For example, an online shop shows the last three products a user looked at. The shop stores them in a fixed-size array and overwrites the oldest slot when a new product comes in. The product page and the email service both need the products newest first, but neither should know which slot holds the newest product. An iterator gives both callers the same simple loop, and the array logic stays inside one class.
1.1. Participants and Their Java Types
The pattern has four roles. In Java, two of them are standard interfaces, so we write only the concrete classes.
| Role | Responsibility | Java type in our example |
|---|---|---|
| Iterator | Declares the traversal methods | java.util.Iterator (hasNext(), next()) |
| Concrete iterator | Keeps the current position and returns the elements | NewestFirstIterator, an inner class of RecentlyViewed |
| Aggregate | Declares a method that creates an iterator | java.lang.Iterable (iterator()) |
| Concrete aggregate | Stores the elements and returns a new iterator on each call | RecentlyViewed |

1.2. Why Not Return the Internal Array?
The simplest alternative is a getter that returns the array or a List. That couples every caller to the storage format. With the ring buffer in our example, the caller would also need to know the start index and the wrap-around rule, and a caller could change the array and break the collection. An iterator hides the storage and gives read access in a defined order, so the class can switch to another structure later without any caller noticing.
2. Building a Custom Iterator in Java
The example stores the recently viewed products in a ring buffer, which is an array where the write position wraps around to index 0 when it reaches the end. The code needs only JDK 25, and all classes with a runnable Main are in the designpatterns repository.
2.1. The Collection (Concrete Aggregate)
The class RecentlyViewed keeps an array, the index of the next free slot, and the number of stored items. When the array is full, add() overwrites the oldest item.
public class RecentlyViewed implements Iterable<String> {
private final String[] items;
private int next; // slot for the next add()
private int size;
private int modCount; // changes on every add(), used for fail-fast iterators
public RecentlyViewed(int capacity) {
if (capacity < 1) {
throw new IllegalArgumentException("capacity must be at least 1");
}
this.items = new String[capacity];
}
public void add(String item) {
items[next] = item;
next = (next + 1) % items.length;
if (size < items.length) {
size++;
}
modCount++;
}
@Override
public Iterator<String> iterator() {
return new NewestFirstIterator();
}
}
The method iterator() returns a new iterator object on every call. Each iterator has its own position, so two loops over the same collection do not affect each other.
After the four add() calls from the intro, the array holds mango in slot 0, because mango overwrote apple. The newest item is always one slot before next, so the iterator starts there and walks backward, wrapping from slot 0 to the last slot.

2.2. The Iterator (Concrete Iterator)
The iterator is a private inner class, so it can read the array of the enclosing RecentlyViewed object, while no other class can create it. It counts the items it has returned and computes the array index from that count.
private class NewestFirstIterator implements Iterator<String> {
private int returned; // items returned so far
private final int expectedModCount = modCount;
@Override
public boolean hasNext() {
return returned < size;
}
@Override
public String next() {
checkForChanges(expectedModCount);
if (!hasNext()) {
throw new NoSuchElementException();
}
int index = Math.floorMod(next - 1 - returned, items.length);
returned++;
return items[index];
}
}
private void checkForChanges(int expectedModCount) {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
We use Math.floorMod() instead of the % operator, because % returns a negative number for a negative left side, e.g. -1 % 3 is -1, whereas Math.floorMod(-1, 3) is 2. That is the wrap-around from slot 0 to the last slot. The fields modCount and expectedModCount make the iterator fail-fast, which we cover in section 4.
2.3. Using the Iterator With for-each
Because RecentlyViewed implements Iterable, the enhanced for-each loop accepts it. The compiler turns the loop into a call to iterator() followed by hasNext() and next() calls. Iterable also brings the default method forEach(), which takes a lambda or a method reference.
Iterator<String> it = recent.iterator();
while (it.hasNext()) {
String item = it.next(); // mango, cherry, banana
}
for (String item : recent) {
System.out.println(item); // mango, cherry, banana
}
List<String> items = new ArrayList<>();
recent.forEach(items::add); // [mango, cherry, banana]
We use the explicit while loop only when we need the iterator object itself, for example to call remove() or to stop early and keep the position for later. In all other cases, the for-each loop is shorter and cannot call next() twice by mistake.
3. Several Traversal Orders for One Collection
One benefit of the pattern is that one collection can offer several iterators. The collection stays the same, and each traversal order is a separate iterator class. For example, the product page shows the recently viewed items newest first, whereas an analytics job wants them oldest first, in the order the user saw them.
We add a second inner class OldestFirstIterator and a method that returns an Iterable for it. Because Iterable has a single abstract method, a constructor reference OldestFirstIterator::new is enough.
public Iterable<String> oldestFirst() {
return OldestFirstIterator::new;
}
private class OldestFirstIterator implements Iterator<String> {
private int returned;
private final int expectedModCount = modCount;
@Override
public boolean hasNext() {
return returned < size;
}
@Override
public String next() {
checkForChanges(expectedModCount);
if (!hasNext()) {
throw new NoSuchElementException();
}
int index = Math.floorMod(next - size + returned, items.length);
returned++;
return items[index];
}
}
The caller picks the order by picking the Iterable. Two iterators over the same collection also keep separate positions, as the last line shows.
for (String item : recent.oldestFirst()) {
System.out.println(item); // banana, cherry, mango
}
Iterator<String> first = recent.iterator();
Iterator<String> other = recent.iterator();
String a = first.next(); // "mango"
String b = first.next(); // "cherry"
String c = other.next(); // "mango" (own position)
4. Rules Every Iterator Must Follow
The Iterator interface has a contract in its Javadoc, and callers such as for-each loops and the Collections API rely on it. A custom iterator that breaks the contract causes bugs far away from the iterator code.
| Situation | Required behavior | Our iterators |
|---|---|---|
| next() after the last element | Throw NoSuchElementException | yes |
| remove() not supported | Throw UnsupportedOperationException | inherited from the default method |
| Collection changed during iteration | Should throw ConcurrentModificationException (fail-fast, best effort) | yes, through modCount |
| hasNext() called many times | Returns the same answer and does not move the position | yes |
4.1. next() and remove()
When there are no more elements, next() must throw NoSuchElementException, never return null, because null can be a valid element. The default remove() method in Iterator throws UnsupportedOperationException with the message remove, so a read-only iterator needs no code for it.
Iterator<String> it = recent.iterator();
it.next();
it.next();
it.next();
boolean hasMore = it.hasNext(); // false
String none = it.next(); // NoSuchElementException
Iterator<String> readOnly = recent.iterator();
readOnly.next();
readOnly.remove(); // UnsupportedOperationException: remove
The safe way to read is to call hasNext() before every next(), which is what the for-each loop does for us.
4.2. Fail-Fast Iterators
A fail-fast iterator throws ConcurrentModificationException as soon as it notices that the collection changed after the iterator was created. Without that check, the iterator would return a mix of old and new elements, or skip some, and nobody would notice. Our collection increases modCount on every add(), and each iterator compares the value with the copy it took at creation.
For example, a loop adds a product to recent while it is still reading recent.
for (String item : recent) {
if (item.equals("cherry")) {
recent.add("kiwi"); // the next next() throws ConcurrentModificationException
}
}
The fix is to change the collection only after the loop, or to loop over a copy. Here we copy the items into a list first, so the loop reads the copy and add() changes only the original.
List<String> snapshot = new ArrayList<>();
recent.forEach(snapshot::add);
for (String item : snapshot) {
if (item.equals("cherry")) {
recent.add("lime"); // no exception
}
}
5. Getting a Stream From an Iterable
An Iterable has no stream() method, unlike Collection. The default method spliterator() of Iterable and the class StreamSupport turn any Iterable into a stream, so our custom collection works with map(), filter() and collectors.
// recent holds lime, kiwi, mango after section 4.2
String joined = StreamSupport.stream(recent.spliterator(), false)
.map(String::toUpperCase)
.collect(Collectors.joining(", ")); // "LIME, KIWI, MANGO"
The second argument false creates a sequential stream. The default spliterator does not know the size and splits poorly, so we keep such streams sequential. The Iterable or Iterator to Stream guide shows more conversions.
6. Iterators in the JDK
We use the iterator pattern every day through the JDK. Every Collection implements Iterable, and several other APIs return iterators as well.
| JDK type | Role | Note |
|---|---|---|
| Iterator | Iterator | Forward only, with optional remove() |
| ListIterator | Iterator | Forward and backward over a List, plus set() and add() |
| Spliterator | Iterator | Can split the elements for parallel streams |
| Scanner | Iterator | Implements Iterator<String> and returns tokens from text input |
| DirectoryStream<Path> | Aggregate | Lists the files of a folder; allows only one iterator |
| Enumeration | Iterator | Legacy interface from Java 1.0; asIterator() converts it |
ArrayList is a good model for a custom collection. Its iterator is an inner class, it checks a modCount field, and it supports remove().
7. Iterator FAQs
7.1. What Is the Difference Between Iterator and Iterable?
An Iterable is a collection that can create iterators, and an Iterator is one pass over that collection. An Iterable has the method iterator(), which returns a new Iterator on each call, whereas an Iterator has hasNext() and next() and is used up after one pass. The for-each loop accepts an Iterable or an array, but not an Iterator.
7.2. How Do I Remove Elements From a List While Iterating?
We call remove() on the iterator, or removeIf() on the list. Calling list.remove() inside a for-each loop changes the list behind the iterator’s back, so the next next() call throws ConcurrentModificationException.
List<String> cart = new ArrayList<>(List.of("apple", "banana", "cherry"));
for (String item : cart) {
if (item.startsWith("a")) {
cart.remove(item); // ConcurrentModificationException on the next loop step
}
}
List<String> safe = new ArrayList<>(List.of("apple", "banana", "cherry"));
Iterator<String> it = safe.iterator();
while (it.hasNext()) {
if (it.next().startsWith("b")) {
it.remove(); // safe: [apple, cherry]
}
}
List<String> simpler = new ArrayList<>(List.of("apple", "banana", "cherry"));
boolean removed = simpler.removeIf(item -> item.startsWith("b")); // true, [apple, cherry]
The fail-fast check is best effort, not a guarantee. If we remove the second-to-last element inside the for-each loop, hasNext() returns false right away, so the loop ends without an exception and never looks at the last element. The same rule applies whenever we remove elements from an ArrayList inside a loop.
7.3. What Is the Difference Between Iterator and Enumeration?
Enumeration is the older interface from Java 1.0, used by Vector and Hashtable. Iterator replaced it in Java 1.2 with shorter method names and an optional remove() method, and the iterators of the standard collections are fail-fast. New code uses Iterator, and Enumeration.asIterator() (Java 9) converts old Enumeration objects.
7.4. Should I Use an Iterator or an Index-Based for Loop?
We use the for-each loop, which uses the iterator, unless we need the index. An index loop is fast on ArrayList, but LinkedList.get(i) walks the list from one end on every call, so an index loop over a LinkedList gets slower as the list grows. The iterator works at the same speed for every collection type, which is the point of the pattern.
8. Conclusion
The iterator pattern separates the way a collection stores its elements from the way callers walk through them. In Java, we implement Iterable on the collection and return an Iterator from iterator(), and the for-each loop and StreamSupport work with our class without extra code.
A custom iterator is worth writing when the storage is not a plain list, as with the ring buffer in RecentlyViewed. One collection can offer several orders through separate iterator classes, and every iterator keeps its own position.
Each iterator must follow the contract. It throws NoSuchElementException at the end, leaves remove() to the default method when removal is not supported, and fails fast when the collection changes during a loop.
9. References
- Iterator JavaDoc (Java 25)
- Iterable JavaDoc (Java 25)
- ConcurrentModificationException JavaDoc (Java 25)
- The enhanced for statement (JLS 14.14.2)
- DirectoryStream JavaDoc (Java 25)
Happy Learning !!
Iterator pattern de-couple the algorithm logic from the data structure. Suppose there is a method which calculates the sum of all numbers provided by an iterator object. It doesn’t matter whether numbers are stored in linked list or binary tree as long as Iterator objects provides a way to traverse all numbers, the method can calculate the sum.
Here can i know where did u used topiciterator
Its used in Main method above while loop