SECTION 1
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.
1994 • Design Patterns Book
1998 • Java 2 Collections Framework
java.util.Iterator interface to replace the older Enumeration class, adding the crucial remove() method.2004 • Enhanced For-Loop (for-each)
ConcurrentModificationException.2014 • Streams & Lambda Expressions
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.
SECTION 2
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.KEY TAKEAWAY
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.SECTION 3
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.
SECTION 4
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.
Common Mistake
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.SECTION 5
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.
SECTION 6
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.
Remove All Names Starting with "A"
- 1
Step 1 — Create the List
Start with an ArrayList containing:["Alice", "Bob", "Anna", "Charlie", "Alex"]. We usenew ArrayList<>(Arrays.asList(...))to create a mutable list.List:[Alice, Bob, Anna, Charlie, Alex] - 2
Step 2 — Obtain an Iterator
CallIterator<String> it = names.iterator();to get an Iterator. The cursor is positioned before "Alice".Cursor: before index 0 - 3
Step 3 — Iterate with hasNext() and next()
Write a while-loop:while (it.hasNext()) { String name = it.next(); ... }. On the first pass,next()returns "Alice". We check: does "Alice" start with "A"? Yes, so we callit.remove()."Alice" removed → List:[Bob, Anna, Charlie, Alex] - 4
Step 4 — Continue Iterating
Next call: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().After full iteration → List:[Bob, Charlie] - 5
Step 5 — Verify the Result
Print the list usingSystem.out.println(names);. All three names starting with "A" have been safely removed. No exception was thrown, and no element was skipped.Output:[Bob, Charlie]✓
SECTION 7
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. |
Comparison of element-removal approaches in Java
WHEN TO USE EACH
SECTION 8
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 |
Iterator basics vs. advanced iteration tools in Java
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.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
list.remove(element) inside a for-each loop causes a ConcurrentModificationException, but calling iterator.remove() does not.Show answer
list.remove(), the list's internal modCount increments, but the hidden Iterator's expectedModCount stays the same. On the next call to next(), the Iterator detects the mismatch and throws the exception. In contrast, iterator.remove() updates both counters simultaneously, keeping them in sync and avoiding the crash.Question 2: BASIC CALCULATION
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?Show answer
Iterator<Integer> it = nums.iterator(); while (it.hasNext()) { if (it.next() < 5) it.remove(); }. The Iterator visits each element: 5 (keep), 12 (keep), 3 (remove), 8 (keep), 1 (remove), 15 (keep). After the loop, the list contains [5, 12, 8, 15]. The Iterator correctly adjusts after each removal so no elements are skipped.Question 3: INTERMEDIATE
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.Show answer
IllegalStateException on the first iteration. The problem is that remove() is called without a preceding call to next(). The Iterator does not know which element to remove because no element has been 'returned' yet. The fix is to call it.next() before it.remove(): while (it.hasNext()) { it.next(); it.remove(); }. This correctly empties the list.Question 4: APPLIED
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.Show answer
int count = 0; Iterator<String> it = tasks.iterator(); while (it.hasNext()) { if (it.next().startsWith("DONE:")) { it.remove(); count++; } } return count;. The Iterator safely removes "DONE: Clean room" and "DONE: Walk dog", so the method returns 2. The remaining list is ["Buy groceries", "Study Java", "Read chapter 5"]. Using Iterator.remove() ensures we don't skip any tasks.Question 5: CRITICAL THINKING
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?Show answer
get(i) method. Sets are unordered collections, so the concept of position does not apply. Instead, you would use an Iterator: Iterator<Integer> it = set.iterator(); while (it.hasNext()) { if (it.next() % 2 == 0) it.remove(); }. After execution, the set would contain {}, since all values happen to be even. The Iterator is especially valuable for Sets because it provides the only way to traverse and selectively remove elements, since neither index-based access nor for-each + set.remove() are safe options.SUMMARY
Summary — Iterator for Safe Traversal and Removal
Summary
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.