Skip to content
Java collections 5 min read

Iterator

An Iterator is Java’s universal cursor for walking through any collection — one element at a time — without exposing the collection’s internal structure. Whether you’re looping over an ArrayList, a HashSet, or a LinkedList, the same three-method API works everywhere.

Why Iterator Exists

Before Java 2 introduced the Collections Framework, every data structure had its own traversal mechanism (Enumeration, index-based loops, etc.). The Iterator interface unified all of that behind a single contract, and it adds one critical feature a plain for loop lacks: safe removal of elements while iterating.

Note: The enhanced for-each loop is syntactic sugar over Iterator — the compiler rewrites it into an iterator loop automatically. See for-each Loop for that syntax.

The Iterator Interface

java.util.Iterator<E> declares three methods:

MethodDescription
boolean hasNext()Returns true if more elements remain
E next()Returns the next element and advances the cursor
void remove()Removes the element last returned by next() (optional operation)

Java 8 added a default forEachRemaining(Consumer<? super E>) method that processes all remaining elements with a lambda.

Basic Usage

Obtain an iterator from any Collection via iterator(), then loop:

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

public class BasicIterator {
    public static void main(String[] args) {
        List<String> languages = new ArrayList<>();
        languages.add("Java");
        languages.add("Python");
        languages.add("Go");

        Iterator<String> it = languages.iterator();
        while (it.hasNext()) {
            String lang = it.next();
            System.out.println(lang);
        }
    }
}

Output:

Java
Python
Go

Always call hasNext() before next(). If you call next() when no elements remain, you get a NoSuchElementException.

Removing Elements While Iterating

This is the main reason to use an explicit Iterator over a for-each loop. Calling collection.remove() inside a for-each throws ConcurrentModificationException; calling iterator.remove() is perfectly safe:

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

public class SafeRemove {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);
        numbers.add(5);

        Iterator<Integer> it = numbers.iterator();
        while (it.hasNext()) {
            int n = it.next();
            if (n % 2 == 0) {
                it.remove(); // removes 2 and 4 safely
            }
        }

        System.out.println(numbers);
    }
}

Output:

[1, 3, 5]

Warning: You must call next() at least once before calling remove(), otherwise you get an IllegalStateException. You also cannot call remove() twice in a row without an intervening next().

forEachRemaining (Java 8+)

If you want to process the rest of the elements with a lambda after some earlier manual iteration, forEachRemaining is handy:

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

public class ForEachRemaining {
    public static void main(String[] args) {
        List<String> items = List.of("A", "B", "C", "D");
        Iterator<String> it = items.iterator();

        // manually consume the first element
        System.out.println("First: " + it.next());

        // process the rest with a lambda
        it.forEachRemaining(s -> System.out.println("Rest: " + s));
    }
}

Output:

First: A
Rest: B
Rest: C
Rest: D

ListIterator — Bidirectional Traversal

For List implementations, you can upgrade to a ListIterator<E>, which extends Iterator with backward traversal and in-place replacement:

Extra MethodDescription
boolean hasPrevious()Returns true if there are elements behind the cursor
E previous()Moves backward and returns the element
int nextIndex()Index of the element that next() would return
int previousIndex()Index of the element that previous() would return
void set(E e)Replaces the last element returned by next() or previous()
void add(E e)Inserts an element before the element that next() would return
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class ListIteratorDemo {
    public static void main(String[] args) {
        List<String> colors = new ArrayList<>();
        colors.add("Red");
        colors.add("Green");
        colors.add("Blue");

        ListIterator<String> lit = colors.listIterator();

        // forward pass — uppercase each element
        while (lit.hasNext()) {
            String c = lit.next();
            lit.set(c.toUpperCase());
        }

        // backward pass
        System.out.print("Reversed: ");
        while (lit.hasPrevious()) {
            System.out.print(lit.previous() + " ");
        }
    }
}

Output:

Reversed: BLUE GREEN RED 

Tip: listIterator(int index) lets you start the cursor at any position rather than the beginning.

The Iterable Interface

Any class that wants to work with the for-each loop must implement java.lang.Iterable<T>, which requires a single method: Iterator<T> iterator(). Every class in the Collections Framework already does this.

You can make your own classes iterable too:

import java.util.Iterator;

public class Range implements Iterable<Integer> {
    private final int start;
    private final int end;

    public Range(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<>() {
            int current = start;

            @Override public boolean hasNext() { return current < end; }
            @Override public Integer next()    { return current++; }
        };
    }

    public static void main(String[] args) {
        for (int n : new Range(1, 6)) {
            System.out.print(n + " ");
        }
    }
}

Output:

1 2 3 4 5 

Under the Hood

Fail-Fast Iterators

Most java.util collection iterators (e.g., ArrayList, HashMap) are fail-fast. Each collection maintains an internal modCount field that increments on every structural change (add, remove, resize). When you call iterator(), the iterator snapshots modCount into its own expectedModCount. On every next() or remove() call it checks:

if (modCount != expectedModCount) throw new ConcurrentModificationException();

This protects you from subtle bugs caused by accidental concurrent modification — but note it is a best-effort check, not a guaranteed thread-safety mechanism.

Fail-Safe Iterators

Iterators in java.util.concurrent collections (e.g., CopyOnWriteArrayList, ConcurrentHashMap) are fail-safe: they operate on a snapshot or use lock-free techniques and never throw ConcurrentModificationException. The trade-off is that changes made after the iterator was created may or may not be visible. See Concurrent Collections.

Bytecode Reality

The compiler transforms this for-each loop:

for (String s : list) { ... }

into approximately:

Iterator<String> $it = list.iterator();
while ($it.hasNext()) {
    String s = $it.next();
    ...
}

This means every for-each over a Collection has the overhead of creating one Iterator object. For small loops this is negligible; the JIT typically inlines and eliminates the allocation entirely.

Iterator vs for-each vs index loop

ApproachBidirectionalSafe removalWorks on sets/mapsOverhead
Index for loopYes (lists)Yes (with care)NoMinimal
for-each loopNoNoYesOne iterator object
IteratorNoYes (it.remove())YesOne iterator object
ListIteratorYesYesLists onlyOne iterator object

Tip: Use a for-each loop for read-only traversal. Switch to an explicit Iterator only when you need to remove elements during iteration.

Last updated June 13, 2026
Was this helpful?