Iterator in Java: How to Loop Through Collections
backend
8 min read
Master the Iterator in Java with real code examples, learn when to use it over a for-each loop, and stop hitting ConcurrentModificationException for good.

Published By: Nelson Djalo | Date: April 8, 2026
Every Java developer has written a for-each loop to walk through a list. What most don't realize is that under the hood, Java is quietly creating an Iterator object and calling hasNext() and next() for you. Once you understand the Iterator directly, you can do things a for-each loop simply cannot - like safely removing elements mid-loop, building your own traversable data structures, or walking a list backwards.
This post breaks down the Iterator interface, shows where it shines, where it fails, and how to write your own.
An Iterator is an interface in the java.util package that gives you a standard way to traverse any collection - one element at a time. It doesn't care whether you're walking an ArrayList, a HashSet, or a custom data structure. The same three methods work everywhere.
Any class that implements Iterable (which includes every Collection in Java) can hand you an Iterator via its iterator() method. That's the contract that makes Java collections so consistent.
List<String> names = List.of("Ada", "Linus", "Grace");
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name = it.next();
System.out.println(name);
}
If you're still getting comfortable with collections, our Java for Beginners course covers the fundamentals from scratch.
The Iterator<E> interface defines three methods. That's it.
hasNext() returns true if there's another element to visit.next() returns the next element and advances the cursor.remove() deletes the last element returned by next() from the underlying collection.List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
int n = it.next();
if (n % 2 == 0) {
it.remove(); // safely removes even numbers
}
}
System.out.println(numbers); // [1, 3, 5]
Calling next() when hasNext() is false throws NoSuchElementException. Calling remove() before next() throws IllegalStateException. Respect the contract.
The for-each loop (for (String s : list)) is syntactic sugar over the Iterator. The compiler generates the exact same hasNext() and next() calls. So why use an Iterator directly?
Use a for-each loop when:
Use an Iterator when:
// For-each - clean but read-only
for (String s : names) {
System.out.println(s);
}
// Iterator - required if you want to remove
Iterator<String> it = names.iterator();
while (it.hasNext()) {
if (it.next().startsWith("A")) {
it.remove();
}
}
The for-each loop cannot call remove(). If you try to modify the collection inside one, you'll hit a ConcurrentModificationException. More on that in a second.
For a deeper look at Java's loop constructs, check out our For Loop in Java breakdown.
Every major collection supports Iterator. Here's how it looks across the big three.
ArrayList - ordered, indexed, allows duplicates.
List<String> langs = new ArrayList<>(List.of("Java", "Python", "Go"));
Iterator<String> it = langs.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
HashSet - unordered, no duplicates. Iteration order is not guaranteed.
Set<Integer> ids = new HashSet<>(Set.of(10, 20, 30));
Iterator<Integer> it = ids.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
HashMap - you iterate over keys, values, or entries. A Map isn't a Collection, so you grab an iterable view first.
Map<String, Integer> scores = new HashMap<>();
scores.put("Ada", 95);
scores.put("Linus", 88);
Iterator<Map.Entry<String, Integer>> it = scores.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
System.out.println(entry.getKey() + " - " + entry.getValue());
}
Lists get a more powerful cousin called ListIterator. It extends Iterator and adds the ability to walk backwards, add elements, and replace elements in place.
Extra methods worth knowing:
hasPrevious() and previous() - walk backwardsadd(E e) - insert an element at the cursorset(E e) - replace the last element returned by next() or previous()nextIndex() and previousIndex() - get the cursor positionList<String> tasks = new ArrayList<>(List.of("write", "review", "ship"));
ListIterator<String> it = tasks.listIterator();
while (it.hasNext()) {
String task = it.next();
if (task.equals("review")) {
it.set("code review"); // replace in place
it.add("test"); // insert after current
}
}
System.out.println(tasks); // [write, code review, test, ship]
ListIterator is only available on List implementations. You won't find it on Set or Map.
This is the bug that trips up every junior developer at least once.
List<String> items = new ArrayList<>(List.of("a", "b", "c"));
for (String s : items) {
if (s.equals("b")) {
items.remove(s); // BOOM: ConcurrentModificationException
}
}
Under the hood, the ArrayList tracks a modCount. When you modify the list outside the Iterator, the next call to next() notices the mismatch and throws. The fix is simple - use the Iterator's own remove() method.
Iterator<String> it = items.iterator();
while (it.hasNext()) {
if (it.next().equals("b")) {
it.remove(); // safe
}
}
Or, if you're on Java 8+, use removeIf():
items.removeIf(s -> s.equals("b"));
Cleaner, shorter, and does the right thing.
You can make any class iterable by implementing Iterable<T> and returning your own Iterator<T>. Here's a range iterator that spits out numbers from start to end.
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<>() {
private int current = start;
@Override
public boolean hasNext() {
return current < end;
}
@Override
public Integer next() {
if (!hasNext()) throw new NoSuchElementException();
return current++;
}
};
}
}
// Usage
for (int i : new Range(1, 5)) {
System.out.println(i); // 1, 2, 3, 4
}
Once you implement Iterable, you get the for-each loop for free. That's the real power of the interface.
Java iterators come in two flavors.
Fail-fast iterators throw ConcurrentModificationException the moment they detect structural modification from outside the Iterator. ArrayList, HashMap, and HashSet all use fail-fast iterators. They're fast because they don't copy anything - they just check a counter.
Fail-safe iterators work on a snapshot or copy of the collection, so modifications during iteration don't blow up. They're found in concurrent collections like CopyOnWriteArrayList and ConcurrentHashMap. The trade-off is that you might not see the latest state.
List<String> safe = new CopyOnWriteArrayList<>(List.of("a", "b", "c"));
for (String s : safe) {
safe.add("d"); // no exception, but "d" isn't seen in this loop
}
Rule of thumb - use fail-safe collections when multiple threads are reading and writing. For single-threaded code, stick with fail-fast.
Since Java 8, streams have become the go-to for most iteration tasks. A stream is a higher-level abstraction that handles iteration for you, supports method chaining, and can run in parallel.
List<String> names = List.of("Ada", "Linus", "Grace", "Alan");
names.stream()
.filter(n -> n.startsWith("A"))
.map(String::toUpperCase)
.forEach(System.out::println);
Streams are great for transformation pipelines, but Iterator still wins when you need imperative control or when you're implementing a data structure from scratch. For comparison and sorting logic inside those pipelines, see Comparable vs Comparator.
Want to go deeper on collections, streams, and the rest of the language? The Java Master Class covers everything from basics to advanced.
What's the difference between Iterator and Iterable in Java?
Iterable is an interface with one method - iterator() - that returns an Iterator. Any class that implements Iterable can be used in a for-each loop. Iterator is the actual object that walks through the elements.
Can I use Iterator with a Map?
Not directly. A Map isn't a Collection. You call keySet(), values(), or entrySet() first, and then get an Iterator from that view.
Why does remove() on an Iterator throw IllegalStateException?
Because you called remove() without calling next() first, or you called remove() twice in a row. The Iterator needs to know which element to delete, and that's the one most recently returned by next().
Is Iterator slower than a for loop?
For ArrayList, an indexed for loop is marginally faster because it skips the Iterator object creation. For LinkedList, the Iterator is dramatically faster because it avoids the O(n) lookup on every get(i) call. Always prefer Iterator or for-each unless you've profiled a hot path.
What happens if I call next() past the end?
It throws NoSuchElementException. Always guard with hasNext().
The Iterator interface is one of those quiet Java fundamentals that unlocks cleaner code once you actually use it. Reach for it when you need to remove elements, walk in both directions, or build your own iterable types - and drop down to streams when you just want to transform data.
Ready to sharpen your Java from the ground up? Start with our Java for Beginners course and build a real foundation.

Skip the generic recommendations. These 9 books changed how I write code, lead teams, and think about systems - from Clean Code to books most devs haven't heard of.

The exact skills, tools, and learning order to go from zero to hired as a Java full stack developer. Covers Spring Boot, React, databases, Docker, and what employers actually look for.

Abstract class or interface? Most Java devs get this wrong. Here's a clear breakdown with a side-by-side comparison table, code examples, and a simple decision rule.
Join thousands of developers mastering in-demand skills with Amigoscode. Try it free today.