Iterator, ListIterator and every way to iterate
Iterator, Iterable, ListIterator, list and map iteration styles, forEach versus stream forEach, Iterable sizing, Spliterator, Enumeration and performance traps.
On this page
🔁 What is iteration in Java?
Iteration is the way you visit elements one at a time without exposing how a collection stores them. An ArrayList walks an array, a LinkedList walks nodes, a HashMap walks buckets, but your loop can still say "give me the next element". Think of an iterator as a bookmark placed between elements: each next() moves the bookmark forward and returns what it crossed.
For this lesson, we'll focus on Iterator, Iterable, ListIterator, every normal way to iterate a List, brief map iteration, Collection.forEach() versus stream().forEach(), sizing an Iterable, Spliterator, legacy Enumeration, and the performance traps interviewers like. This is asked often because the syntax looks easy, but the cursor rules, removal rules and LinkedList.get(i) trap separate API familiarity from real understanding.
Below the line (out of scope)
- Removing while iterating and
ConcurrentModificationExceptionin depth: ConcurrentModificationException explained - Fail-fast, weakly consistent and snapshot iterators: Fail-fast vs fail-safe iterators
- Map iteration, merging, comparing and sorting maps: Everyday Map operations
- Choosing
ArrayListvsLinkedList: ArrayList vs LinkedList
The 30-second answer
Iterable means "this object can produce an Iterator", and the enhanced for loop is syntax over that iterator for collections. Iterator gives hasNext, next and optional remove; call remove only once after a successful next. ListIterator is the list-only upgrade: it moves both directions and can set or add at the cursor. Use enhanced for for read-only traversal, an explicit iterator when you must remove safely, a ListIterator for bidirectional edits, forEach for a local side effect, and streams for transformations and reductions. Avoid for (int i = 0; i < list.size(); i++) list.get(i) on LinkedList, because it becomes O(n^2). Java 21 adds reversed() on sequenced collections, so many reverse loops no longer need index arithmetic.
What you’ll learn
- Use
Iteratorcorrectly withhasNext,nextand **remove** - Explain the **
Iterablecontract** and write a small custom iterable range - Use
ListIteratorfor bidirectional traversal,set,addand cursor indexes - Choose among index loops, enhanced
for,Iterator,ListIterator,forEach, streams and Java 21reversed() - Compare
Collection.forEach()withCollection.stream().forEach()for ordering, mutation and synchronization - Get an
Iterablesize safely usingCollection.size, manual counting or a spliterator-backed stream - Recognize
Spliterator, legacyEnumerationand theLinkedList.get(i)O(n^2) trap
Interview radar
Asked very often: How does enhanced for work, and how do you remove while iterating?
Senior rounds probe: Collection.forEach() vs stream().forEach(), Spliterator characteristics, Java 21 reversed(), and why an index loop over LinkedList is a trap.
Production angle: A loop that skips elements after removal, a synchronized list streamed without holding its lock, or a hot path that turns a linked list traversal into O(n^2).
01Picture it first: the cursor sits between elements
The easiest way to understand iterator bugs is to stop picturing the cursor on an element. It sits between elements. next() crosses the element on the right, previous() crosses the element on the left, and remove() or set() acts on the last element crossed.
That is why remove() before next() is illegal, and why two removes in a row are illegal. The iterator needs one clear "last returned" element.
Iterator shows a cursor after A with next and remove behavior, while ListIterator shows a cursor between B and C with next, previous and add at the cursor
Notice the difference. A plain Iterator moves forward. A ListIterator can move both ways because a List has positions, previous elements and insertion points.
02Iterator and Iterable under the hood
Iterable<T> is a tiny contract: it has Iterator<T> iterator(). That one method is enough for enhanced for, forEach, and default spliterator support. A class does not need to be a Collection to be iterable.
Try it yourself first
Before reading the implementation, write a Range that lets you say for (int n : new Range(3, 6)). Which object owns the cursor?
import java.util.Iterator;
import java.util.NoSuchElementException;
final class Range implements Iterable<Integer> {
private final int startInclusive;
private final int endExclusive;
Range(int startInclusive, int endExclusive) {
this.startInclusive = startInclusive;
this.endExclusive = endExclusive;
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
private int next = startInclusive;
@Override
public boolean hasNext() {
return next < endExclusive;
}
@Override
public Integer next() {
if (!hasNext()) throw new NoSuchElementException();
return next++;
}
};
}
}
Notice three things:
- The
Rangeis reusable because each call toiterator()returns a fresh cursor. next()must throwNoSuchElementExceptionafter the end. Returningnullwould be wrong becausenullcan be a valid element in some collections.- We did not implement
remove(), so the default method throwsUnsupportedOperationException.
The enhanced for loop over an Iterable compiles roughly to this shape:
for (Iterator<Integer> it = range.iterator(); it.hasNext(); ) {
Integer n = it.next();
System.out.println(n);
}
Arrays are the special case: enhanced for over an array compiles to an index loop, not to an Iterator. For everything that implements Iterable, the loop asks for an iterator and uses hasNext() and next().
Removing with Iterator
Iterator.remove() removes the last element returned by next(). It is the safe removal tool because it updates the iterator's own expected modification state. Removing directly from many collections while an iterator is active can trigger ConcurrentModificationException; we cover the details in ConcurrentModificationException explained.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
List<String> users = new ArrayList<>(List.of("active", "disabled", "active"));
for (Iterator<String> it = users.iterator(); it.hasNext(); ) {
String status = it.next();
if (status.equals("disabled")) {
it.remove();
}
}
System.out.println(users); // [active, active]
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- ListIterator: the cursor that can turn around
- Every way to iterate over a list
- Iterating maps, briefly
- Advanced: forEach, streams, sizing and old cursors
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal
Further reading (10)
- Iterator (Java SE 21 API)
- Iterable (Java SE 21 API)
- ListIterator (Java SE 21 API)
- Collection.forEach (Java SE 21 API)
- Stream.forEach (Java SE 21 API)
- Spliterator (Java SE 21 API)
- SequencedCollection.reversed (Java SE 21 API)
- Collections.synchronizedList (Java SE 21 API)
- Enumeration (Java SE 21 API)
- JEP 431: Sequenced Collections