Showing posts with label ava. Show all posts
Showing posts with label ava. Show all posts

Iterator Design Pattern in Java: Complete Guide with Examples

Iterator Design Pattern in Java: Complete Guide with Examples

Iterator Design Pattern in Java: Complete Guide with Real-World Examples

Have you ever needed to process hundreds of employees, orders, products, or transactions one by one? You could access every element using indexes, but what happens when the underlying data structure changes from an array to a linked list or a custom collection?

This is where the Iterator Design Pattern in Java becomes useful. It provides a consistent way to traverse a collection without exposing the internal details of how that collection stores its data.

In this guide, we will build a small employee directory, implement a custom iterator, explore Java's built-in Iterator and Iterable interfaces, and discuss practical use cases, limitations, and interview questions.

1. What Is the Iterator Design Pattern?

The Iterator is a behavioral design pattern that provides a standard way to access elements of a collection sequentially without exposing its internal representation.

Imagine an employee directory that internally uses an array. The application reading employee records should not need to know whether the records are stored in an array, a linked list, or another data structure. It should simply request the next employee.

An iterator keeps track of the traversal position and provides operations for checking whether another element exists and retrieving that element. This separates the traversal logic from the collection itself.

A simple real-life analogy

Think about a music playlist. You do not need to know how the application stores every song internally. You use Next to move to the next song and continue until the playlist ends. An iterator follows a similar idea when traversing data.

In one sentence: The Iterator pattern lets you move through a collection without depending on its internal data structure.

2. The Problem Without an Iterator

Suppose an application stores employees in an array:

String[] employees = {
    "Aman",
    "Priya",
    "Rahul"
};

for (int i = 0; i < employees.length; i++) {
    System.out.println(employees[i]);
}

This works, but the loop depends on the array's indexing and length.

Now suppose the implementation changes to a linked list or a custom data structure. The traversal logic may need to change as well. If several services contain similar loops, maintaining those loops becomes harder.

There is another concern: the calling code may need to understand how to access elements, even though that detail belongs to the collection.

What the Iterator pattern changes

  • The collection manages its data.
  • The iterator manages the traversal position.
  • The client uses a consistent interface to retrieve elements.
  • The collection can change its internal representation without requiring the client to use indexes.

This separation improves maintainability when an application works with different kinds of collections.

3. How the Iterator Pattern Works

The pattern commonly involves three main participants:

  1. Aggregate: Defines how a collection provides an iterator.
  2. Concrete Aggregate: Stores the data and creates a suitable iterator.
  3. Iterator: Defines how elements are accessed during traversal.

A concrete iterator implements the traversal rules for the underlying collection.

Typical execution flow

  1. The client asks the collection for an iterator.
  2. The iterator starts at the initial traversal position.
  3. The client calls hasNext() to check whether another element exists.
  4. The client calls next() to retrieve the next element.
  5. The process continues until no elements remain.

Java's java.util.Iterator<E> provides this familiar interface through methods such as hasNext() and next(). See the official Java Iterator documentation.

4. Iterator Design Pattern Diagram

The following original diagram shows how the client interacts with a collection through an iterator rather than accessing the internal storage directly.

Iterator Design Pattern in Java A client requests an iterator from an employee collection. The iterator traverses internal employee data and returns one employee at a time to the client. Client Consumes elements Iterator Tracks position Collection Stores employees request accesses creates hasNext() / next() Internal employee data Employee A Employee B Employee C
Figure: The client traverses employee records through an iterator, without needing to know the storage details.

5. Iterator and Iterable in Java

Java already supports the Iterator pattern through the Collections Framework. Before creating a custom implementation, it is worth understanding two interfaces.

Iterator<E>

The Iterator interface represents an object that traverses elements one at a time.

Method Purpose
hasNext() Returns true when another element is available.
next() Returns the next element or throws NoSuchElementException when exhausted.
remove() Optionally removes the last element returned by this iterator. Support depends on the implementation.
forEachRemaining(action) Processes the remaining elements using the supplied action.

Iterable<E>

The Iterable interface represents an object that can provide an iterator. It defines the iterator() method and enables enhanced for-loop syntax.

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class IteratorBasics {
    public static void main(String[] args) {
        List<String> names =
                Arrays.asList("Aman", "Priya", "Rahul");

        Iterator<String> iterator = names.iterator();

        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        // Iterable collections also support this:
        for (String name : names) {
            System.out.println(name);
        }
    }
}

Important distinction: Iterable provides the iterator; Iterator performs the traversal.

6. Real-World Java Implementation: Employee Directory

Imagine a small HR application that stores employee names internally in an array. The application should be able to read employees sequentially without directly accessing that array.

We will create:

  • EmployeeIterator — the iterator contract.
  • EmployeeDirectory — the collection and its internal storage.
  • EmployeeDirectoryIterator — the concrete traversal implementation.
  • IteratorPatternDemo — the client that uses the iterator.

Step 1: Create the EmployeeIterator interface

public interface EmployeeIterator {
    boolean hasNext();

    String next();
}

This interface defines the two operations the client needs to traverse employee names.

Step 2: Create the EmployeeDirectory

public class EmployeeDirectory {

    private final String[] employees;

    public EmployeeDirectory(String[] employees) {
        this.employees = employees.clone();
    }

    public EmployeeIterator iterator() {
        return new EmployeeDirectoryIterator(employees);
    }
}

The directory keeps its array private and returns an iterator when requested. Cloning the input array prevents changes to the original array from altering the directory's storage.

The array is not exposed through a public getter. This keeps the client independent of the internal representation.

Step 3: Implement the concrete iterator

public class EmployeeDirectoryIterator
        implements EmployeeIterator {

    private final String[] employees;
    private int position;

    public EmployeeDirectoryIterator(String[] employees) {
        this.employees = employees;
        this.position = 0;
    }

    @Override
    public boolean hasNext() {
        return position < employees.length;
    }

    @Override
    public String next() {
        if (!hasNext()) {
            throw new java.util.NoSuchElementException(
                    "No more employees available"
            );
        }

        return employees[position++];
    }
}

Notice how the iterator maintains its own position. Every successful call to next() returns the current employee and advances the position by one.

When no employees remain, next() throws NoSuchElementException instead of accessing an invalid array index.

Step 4: Use the iterator in the client

public class IteratorPatternDemo {

    public static void main(String[] args) {
        String[] names = {
            "Aman",
            "Priya",
            "Rahul",
            "Neha"
        };

        EmployeeDirectory directory =
                new EmployeeDirectory(names);

        EmployeeIterator iterator = directory.iterator();

        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

Output

Aman
Priya
Rahul
Neha

What did we achieve?

  • The client does not use array indexes.
  • The directory controls how an iterator is created.
  • The iterator controls the current traversal position.
  • The client can traverse the data through a small, consistent interface.

This example illustrates the core pattern. In production code, prefer Java's standard Iterator and Iterable interfaces unless a custom traversal contract provides a real benefit.

7. Using Iterators with Java Collections

In day-to-day development, you will usually work with built-in iterators instead of writing a new iterator for every collection.

Example 1: Iterate through an ArrayList

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ArrayListIteratorExample {

    public static void main(String[] args) {
        List<String> orders = new ArrayList<>();

        orders.add("ORD-101");
        orders.add("ORD-102");
        orders.add("ORD-103");

        Iterator<String> iterator = orders.iterator();

        while (iterator.hasNext()) {
            String orderId = iterator.next();
            System.out.println("Processing order: " + orderId);
        }
    }
}

This approach works without the client needing to know how the list stores its elements.

Example 2: Remove an element safely during iteration

Suppose an application needs to remove cancelled orders while traversing a list. When the collection supports iterator removal, use the iterator's own remove() method.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class RemoveOrdersExample {

    public static void main(String[] args) {
        List<String> orders = new ArrayList<>(
                List.of("ORD-101", "CANCELLED", "ORD-103")
        );

        Iterator<String> iterator = orders.iterator();

        while (iterator.hasNext()) {
            String order = iterator.next();

            if ("CANCELLED".equals(order)) {
                iterator.remove();
            }
        }

        System.out.println(orders);
    }
}

Output

[ORD-101, ORD-103]

Important: Iterator removal is optional. Some iterators do not support it and throw UnsupportedOperationException. For Java versions before Java 9, replace List.of(...) with a compatible list construction.

Example 3: Use forEachRemaining()

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class ForEachRemainingExample {

    public static void main(String[] args) {
        List<String> services =
                Arrays.asList("Payment", "Order", "Notification");

        Iterator<String> iterator = services.iterator();

        iterator.forEachRemaining(
                service -> System.out.println("Service: " + service)
        );
    }
}

This method is useful when you want to process all remaining elements with a single action.

8. Iterator Design Pattern in Spring Boot

Consider a Spring Boot service that retrieves a batch of transaction IDs from a repository and processes each transaction.

import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class TransactionProcessingService {

    public void processTransactions(List<String> transactionIds) {

        var iterator = transactionIds.iterator();

        while (iterator.hasNext()) {
            String transactionId = iterator.next();

            processTransaction(transactionId);
        }
    }

    private void processTransaction(String transactionId) {
        // Apply transaction validation and business processing.
        System.out.println("Processing: " + transactionId);
    }
}

The service can traverse a list without using indexes or depending on the list's concrete implementation.

Where does this help in a real application?

  • Processing a batch of payment or payout records.
  • Traversing validation results returned by a service.
  • Reading records from an in-memory collection.
  • Applying a common operation to elements returned by different collection implementations.

Production consideration: An iterator does not automatically make database processing scalable or memory-efficient. If a query returns millions of records, consider database pagination, streaming with properly managed resources, batch processing, and transaction boundaries. A simple list iterator traverses only the data already held by that list.

9. Fail-Fast Iterators and Concurrent Modification

One common Java interview question is what happens when a collection is modified while an iterator is traversing it.

Many standard collection iterators, including those for ArrayList, are fail-fast on a best-effort basis. A structural modification made directly to the collection during iteration can cause a ConcurrentModificationException.

Problematic example

import java.util.ArrayList;
import java.util.List;

public class ConcurrentModificationExample {

    public static void main(String[] args) {
        List<String> names = new ArrayList<>(
                List.of("Aman", "Priya", "Rahul")
        );

        for (String name : names) {
            if ("Priya".equals(name)) {
                names.remove(name);
            }
        }
    }
}

With an ArrayList, directly removing an element from the list in this loop can cause a ConcurrentModificationException. The exact point at which it is detected is not a guarantee to rely on.

Safer alternative: use the iterator's remove method

Iterator<String> iterator = names.iterator();

while (iterator.hasNext()) {
    String name = iterator.next();

    if ("Priya".equals(name)) {
        iterator.remove();
    }
}

Only use this when the iterator supports removal. Alternatively, for simple filtering, use removeIf() where appropriate:

names.removeIf(name -> "Priya".equals(name));

Do not confuse fail-fast behavior with thread safety. A fail-fast iterator is not a synchronization mechanism, and the exception is not guaranteed to detect every concurrent modification.

10. Advantages and Disadvantages of the Iterator Pattern

Advantages

  • Encapsulation: Clients do not need direct access to the collection's internal storage.
  • Consistent traversal: Different collection implementations can expose a common traversal interface.
  • Separation of responsibilities: The collection stores data while the iterator tracks traversal state.
  • Multiple traversals: Separate iterators can maintain separate positions over the same collection, subject to that collection's modification rules.
  • Maintainability: Clients can avoid index-based logic tied to a particular structure.

Disadvantages

  • Additional abstraction: A custom iterator adds code and types that may not be necessary for a simple collection.
  • Traversal limitations: A basic iterator normally moves forward; it does not automatically provide random access or backward navigation.
  • Modification concerns: Changes to the underlying collection may affect iteration or trigger exceptions, depending on the implementation.
  • Not a performance shortcut: Using an iterator does not automatically improve performance or reduce memory usage.

For ordinary Java collections, the built-in interfaces are usually sufficient. Build a custom iterator when your data structure needs its own traversal rules.

11. Iterator vs Other Approaches

Iterator vs enhanced for loop

Iterator Enhanced for loop
Explicitly controls calls to hasNext() and next(). Provides concise syntax for traversing an Iterable or an array.
Can call remove() when supported. Does not directly expose the iterator for removal.
Useful when the traversal state must be managed explicitly. Useful for straightforward traversal.

Iterator vs ListIterator

Iterator ListIterator
Supports forward traversal. Supports both forward and backward traversal of lists.
Does not expose an index. Provides methods such as nextIndex() and previousIndex().
Available for collections that provide an iterator. Specific to lists.

Iterator vs Stream API

Iterator Stream API
Provides explicit, sequential element traversal. Describes a pipeline of operations such as filtering, mapping, and collecting.
Useful when control flow depends on each element as it is retrieved. Useful for declarative data-processing pipelines.
Does not inherently provide filtering or aggregation operations. Supports intermediate and terminal operations for data processing.

Iterator vs Strategy pattern

The Iterator pattern focuses on how elements are traversed. The Strategy pattern focuses on selecting an interchangeable algorithm or behavior.

For example, iterating over payment transactions is an Iterator use case. Selecting a fee-calculation algorithm for different payment methods is a Strategy use case.

12. Best Practices for Experienced Java Developers

  1. Prefer standard interfaces. Implement Iterable and return Iterator when integrating a custom collection with Java's normal iteration mechanisms.
  2. Follow the iterator contract. hasNext() should report whether another element exists. next() should throw NoSuchElementException when exhausted.
  3. Keep traversal state in the iterator. Avoid putting one shared cursor in the collection if callers should be able to perform independent traversals.
  4. Document mutation behavior. Be clear about whether an iterator supports removal and what happens if the underlying collection changes.
  5. Do not assume thread safety. Use suitable concurrent collections or explicit synchronization when concurrent access is required.
  6. Consider data volume. Traversing an already-loaded list and streaming database results are different concerns. Design large-data processing with memory use and resource management in mind.
  7. Avoid unnecessary custom code. Use built-in collection iterators unless custom traversal behavior provides a meaningful benefit.
  8. Test edge cases. Test empty collections, single-element collections, exhausted iterators, and any supported removal behavior.

13. Java Interview Questions and Answers

Q1. What is the Iterator Design Pattern?

It is a behavioral design pattern that provides a way to traverse a collection sequentially without exposing its internal representation.

Q2. Why is Iterator called a behavioral design pattern?

It describes how objects collaborate to perform an operation—in this case, traversing a collection—rather than how objects are created or structured.

Q3. What is the difference between Iterator and Iterable?

Iterable exposes an iterator() method. Iterator represents the traversal itself through methods such as hasNext() and next().

Q4. What happens when next() is called after all elements have been consumed?

A correctly implemented Java iterator throws NoSuchElementException when no next element is available.

Q5. Can an iterator remove elements?

Yes, if that iterator supports removal. The operation is optional, and unsupported implementations may throw UnsupportedOperationException.

Q6. Can two iterators traverse the same collection independently?

Usually, yes. Each iterator typically maintains its own traversal position. Modifications to the collection can affect the behavior of either iterator according to that collection's contract.

Q7. What is ConcurrentModificationException?

It is an exception that some iterators may throw when they detect a modification that violates their expected iteration rules. Fail-fast detection is best-effort and must not be relied on for thread safety.

Q8. What is the difference between Iterator and ListIterator?

Iterator supports forward traversal. ListIterator adds backward traversal, list-position information, and additional modification operations for lists.

Q9. Is Iterator better than a for loop?

Neither is universally better. An enhanced for loop is convenient for ordinary traversal. An explicit iterator is useful when you need direct control over traversal or supported iterator operations.

Q10. Where is the Iterator pattern used in real projects?

It is used whenever application code traverses collections, such as employee records, payment batches, order lists, search results, and in-memory processing pipelines.

Q11. Does Iterator improve performance?

Not automatically. Its primary benefit is separating traversal from storage. Actual performance depends on the collection, iterator implementation, workload, and surrounding code.

Q12. When should you implement a custom iterator?

When a custom data structure needs traversal rules that the standard collection implementations do not provide. For ordinary lists, sets, and queues, use their built-in iterators.

14. Frequently Asked Questions

Is Iterator a class or an interface in Java?

java.util.Iterator is a generic interface. Concrete classes provide implementations for their collections.

Does every Java collection support Iterator?

Java's standard Collection interface extends Iterable, so collection implementations provide an iterator. A Map is not a Collection, but it offers collection views such as keySet(), values(), and entrySet() that can be traversed.

Can I reuse an exhausted iterator?

There is no standard reset operation in Iterator. Obtain a new iterator from the collection when you need to traverse it again, assuming the collection supports that operation.

Does an iterator copy the collection?

Not necessarily. Most iterators maintain traversal state and refer to the underlying collection. Their exact behavior depends on the implementation.

Is Iterator part of the Gang of Four design patterns?

Yes. Iterator is one of the behavioral patterns described in the Gang of Four design-pattern catalog.

Can Iterator be used with a database cursor?

A cursor-like API can use the same general idea of sequential traversal. However, database cursors and streaming APIs have additional resource-management and lifecycle concerns that a normal collection iterator does not solve by itself.

15. Quick Revision Checklist

  • Iterator is a behavioral design pattern.
  • It separates traversal from the collection's internal representation.
  • Iterator provides traversal operations.
  • Iterable provides an iterator.
  • hasNext() checks whether another element exists.
  • next() retrieves the next element.
  • remove() is optional and depends on the implementation.
  • Fail-fast behavior is not a thread-safety guarantee.
  • Java collections already provide iterators for common use cases.
  • Custom iterators are most useful when custom traversal rules are needed.

Conclusion

The Iterator Design Pattern solves a straightforward but important problem: how can application code traverse data without depending on the way that data is stored?

Java makes this pattern practical through the Iterator and Iterable interfaces. Once you understand how these interfaces work, you can write cleaner collection processing code, handle supported removals correctly, and recognize situations where a custom iterator is useful.

For everyday development, start with the iterators already provided by Java collections. Implement your own only when the data structure needs a specific traversal behavior.

If you are learning Java design patterns for interviews or real projects, continue with related patterns such as Strategy, Observer, Factory, and Chain of Responsibility.