Historical Context & Motivation
Before Java introduced its Collections Framework in 1998, programmers often relied on plain arrays and manual index tracking to manage groups of objects. This approach was error-prone and led to subtle bugs, especially when developers tried to add or remove elements while iterating through a data structure. The Iterator pattern emerged as a disciplined way to walk through a collection one element at a time, and to remove elements safely without crashing the program.
java.util.Iterator interface to replace the older Enumeration class, adding the crucial remove() method.ConcurrentModificationException.The core question that Iterator answers is straightforward: How can you loop through a list and safely remove certain elements without corrupting the list or crashing the program? As you'll see, trying to remove elements with a regular for-each loop causes Java to throw an exception, and using a traditional index-based for-loop can cause you to skip elements accidentally.
Core Principles & Definitions
An Iterator is an object that lets you step through a collection one element at a time. Think of it as a cursor or pointer that starts just before the first element and moves forward each time you call next(). Every Java collection — ArrayList, HashSet, LinkedList, and more — can provide an Iterator through its iterator() method.
hasNext()
true if the Iterator has more elements to visit. This is the guard condition of your while-loop — it prevents you from going past the end.next()
next() when hasNext() is false throws a NoSuchElementException.remove()
next(). This is the only safe way to delete elements during iteration. It must be called at most once per next() call.ConcurrentModificationException
list.remove() directly.next()), you can check if there are more pages (hasNext()), and you can rip out the page you just read (remove()) without losing your place. If someone else rips out a different page while you're reading, the book is corrupted — that's the ConcurrentModificationException.Visual Explanation — How the Iterator Cursor Moves
The diagram below shows an ArrayList with four string elements. The Iterator's cursor starts before the first element. Each call to next() moves the cursor forward and returns the element it just passed. When remove() is called, the element most recently returned by next() is deleted, and the remaining elements shift to fill the gap.
next() call advances it and returns the element it passed over. Calling remove() deletes that element and the cursor adjusts so no elements are skipped.Notice how in State 5 the list has only three elements, and the cursor is positioned so that next() will return "Cat" — the element that shifted into the removed slot. This is the key advantage of the Iterator: it adjusts its internal position automatically after a removal, so you never skip an element or get an index out of bounds error.
How It Works — The Fail-Fast Mechanism
Java collections use an internal counter called modCount (modification count) that increments every time the collection is structurally changed — meaning an element is added or removed. When you create an Iterator, it records the current value of modCount as expectedModCount. Every time you call next() or remove() on the Iterator, it checks whether modCount still matches expectedModCount. If they differ, the Iterator knows someone modified the collection outside of the Iterator, and it immediately throws a ConcurrentModificationException.
Why list.remove() Inside a For-Each Loop Fails
The enhanced for-each loop (for (String s : list)) is syntactic sugar — the compiler converts it into an Iterator loop behind the scenes. If you call list.remove(s) inside that loop, you are modifying the collection directly (incrementing modCount), but the hidden Iterator's expectedModCount stays the same. The next call to next() detects the mismatch and throws the exception.
Why Iterator.remove() Succeeds
When you call iterator.remove() instead, the Iterator updates both modCount and expectedModCount simultaneously. The two counters stay in sync, so no exception is thrown. This is why Iterator.remove() is the only safe way to remove elements during iteration.
remove() twice in a row without calling next() between them. The Iterator needs to know which element to remove, and it can only remove the one most recently returned by next(). Calling remove() without a preceding next() throws an IllegalStateException.Code Patterns — Right vs. Wrong
Let's compare three approaches to removing all strings shorter than 4 characters from an ArrayList<String>. The diagram below places them side by side so you can see the differences clearly.
list.remove() changes modCount without updating the hidden Iterator. Center: index-based removal works but requires manual index adjustment. Right: Iterator.remove() is the recommended, safe approach.The key difference is highlighted in each code panel. In the for-each version, list.remove(s) modifies the list directly. In the index-based version, you must manually decrement i after removal — forget that step, and you'll silently skip the next element. The Iterator pattern handles all bookkeeping for you, making it the cleanest and most reliable approach.
Worked Example — Filtering a Student Roster
Imagine you have an ArrayList<String> containing student names, and you need to remove every name that starts with the letter "A". Let's walk through this using an Iterator.
["Alice", "Bob", "Anna", "Charlie", "Alex"]. We use new ArrayList<>(Arrays.asList(...)) to create a mutable list.[Alice, Bob, Anna, Charlie, Alex]Iterator<String> it = names.iterator(); to get an Iterator. The cursor is positioned before "Alice".while (it.hasNext()) { String name = it.next(); ... }. On the first pass, next() returns "Alice". We check: does "Alice" start with "A"? Yes, so we call it.remove().[Bob, Anna, Charlie, Alex]next() returns "Bob". Does "Bob" start with "A"? No, so we skip. Next: "Anna" starts with "A" → it.remove(). Next: "Charlie" → skip. Next: "Alex" starts with "A" → it.remove().[Bob, Charlie]System.out.println(names);. All three names starting with "A" have been safely removed. No exception was thrown, and no element was skipped.[Bob, Charlie] ✓Strengths & Limitations of Iterator
| Approach | Strengths | Limitations |
|---|---|---|
| Iterator | Safe removal during traversal; works with any Collection (List, Set, Queue); no manual index management needed. | Can only move forward (no going back); can only remove, not add; slightly more verbose than for-each. |
| For-Each Loop | Clean, readable syntax; great for read-only traversal; handles any Iterable. | Cannot safely modify the collection during iteration; causes ConcurrentModificationException if you try. |
| Index For-Loop | Full control over index; can go forward, backward, or skip elements; allows add and remove. | Only works with Lists (not Sets); must manually adjust index after removal; error-prone. |
| removeIf() (Java 8+) | One-liner; internally uses Iterator; accepts a lambda predicate; very concise. | Less control over the loop body; only performs removal, not other operations during traversal. |
Connection to Advanced Concepts
The basic Iterator interface is just the beginning. Java provides several more advanced tools that build on the same idea of safe traversal. Understanding Iterator now prepares you for these more powerful concepts later.
| Basic (This Lesson) | Advanced (Future Study) |
|---|---|
Iterator<E> — forward only, remove only | ListIterator<E> — bidirectional, supports add, set, and remove; provides index info |
| Fail-fast behavior — throws exception on concurrent modification | Concurrent collections (e.g., CopyOnWriteArrayList) — allow safe modification from multiple threads without exceptions |
while (it.hasNext()) manual loop | stream().filter().collect() — functional pipeline that creates a new collection instead of modifying the original |
collection.removeIf(predicate) — single condition | Spliterator<E> — designed for parallel processing of large data sets; used internally by Streams |
As you progress in Java, you'll discover that the Iterator design pattern appears everywhere — in database result sets, file readers, and network streams. Mastering the basics of hasNext(), next(), and remove() gives you a mental model that transfers directly to all of these contexts.
Practice Problems
list.remove(element) inside a for-each loop causes a ConcurrentModificationException, but calling iterator.remove() does not.ArrayList<Integer> nums = new ArrayList<>(Arrays.asList(5, 12, 3, 8, 1, 15));, write code using an Iterator to remove all values less than 5. What does the list contain after the loop finishes?Iterator<String> it = words.iterator(); while (it.hasNext()) { it.remove(); }. This code compiles but crashes at runtime. Identify the error and explain why it fails.ArrayList<String> of tasks: ["Buy groceries", "DONE: Clean room", "Study Java", "DONE: Walk dog", "Read chapter 5"]. Write a method that uses an Iterator to remove all tasks that start with "DONE:" and returns the count of tasks removed.HashSet<Integer> containing the values {10, 20, 30, 40, 50}. Could you use an index-based for-loop to remove even numbers from this Set? Why or why not? What approach would you use instead, and why is the Iterator particularly valuable for Sets?Summary — Iterator for Safe Traversal and Removal
The Iterator interface provides three essential methods: hasNext() checks if more elements exist, next() advances the cursor and returns the current element, and remove() safely deletes the most recently returned element. The for-each loop silently uses an Iterator, but calling list.remove() inside it causes a ConcurrentModificationException because the list's modCount gets out of sync with the Iterator's expectedModCount.
When you need to remove elements while looping, always use an explicit Iterator and call it.remove() — never collection.remove(). This pattern works with any Collection type — Lists, Sets, and Queues alike — making it one of the most versatile and important patterns in the Java Collections Framework. For simple removal conditions in Java 8+, removeIf() offers a concise alternative that uses Iterator internally.