JAVA • COLLECTIONS AND STRINGS

Using Iterator — Use Iterator to traverse and remove safely (intro)

Learn why Java's Iterator is the only safe way to remove elements while looping through a collection.

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
The "Gang of Four" published Design Patterns, formalizing the Iterator pattern as a way to access elements of a collection sequentially without exposing its internal structure.
1998
Java 2 Collections Framework
Java 1.2 shipped with the Collections Framework, introducing the java.util.Iterator interface to replace the older Enumeration class, adding the crucial remove() method.
2004
Enhanced For-Loop (for-each)
Java 5 introduced the enhanced for-loop, which uses an Iterator behind the scenes. However, programmers discovered that modifying a collection inside a for-each loop causes a ConcurrentModificationException.
2014
Streams & Lambda Expressions
Java 8 added Streams and lambda expressions, offering new ways to filter collections. Despite these additions, the classic Iterator remains essential for in-place removal during traversal.

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.

1

hasNext()

Returns 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.
2

next()

Advances the cursor and returns the next element. Calling next() when hasNext() is false throws a NoSuchElementException.
3

remove()

Removes the element most recently returned by next(). This is the only safe way to delete elements during iteration. It must be called at most once per next() call.
4

ConcurrentModificationException

An exception Java throws when a collection is structurally modified (element added or removed) outside of the Iterator while you are iterating. The for-each loop triggers this if you call list.remove() directly.
✦ KEY TAKEAWAY
Think of an Iterator like a bookmark in a novel. You move the bookmark forward one page at a time (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.

The Iterator cursor starts before the first element. Each 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.

⚠️ Common Mistake
Never call 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.

Left: the for-each approach crashes because 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.

Remove All Names Starting with "A"
1
Step 1 — Create the ListStart with an ArrayList containing: ["Alice", "Bob", "Anna", "Charlie", "Alex"]. We use new ArrayList<>(Arrays.asList(...)) to create a mutable list.
List: [Alice, Bob, Anna, Charlie, Alex]
2
Step 2 — Obtain an IteratorCall Iterator<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 call it.remove().
"Alice" removed → List: [Bob, Anna, Charlie, Alex]
4
Step 4 — Continue IteratingNext 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 ResultPrint the list using System.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] ✓

Strengths & Limitations of Iterator

Comparison of element-removal approaches in Java
ApproachStrengthsLimitations
IteratorSafe 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 LoopClean, readable syntax; great for read-only traversal; handles any Iterable.Cannot safely modify the collection during iteration; causes ConcurrentModificationException if you try.
Index For-LoopFull 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.
💡 WHEN TO USE EACH
Use the for-each loop when you only need to read elements. Use an Iterator when you need to remove elements while looping. Use removeIf() if your removal logic fits into a simple condition and you're on Java 8+. Think of it like choosing between walking through a museum (for-each), walking and occasionally removing paintings you don't like (Iterator), or handing a list to a museum assistant who removes them for you (removeIf).

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.

Iterator basics vs. advanced iteration tools in Java
Basic (This Lesson)Advanced (Future Study)
Iterator<E> — forward only, remove onlyListIterator<E> — bidirectional, supports add, set, and remove; provides index info
Fail-fast behavior — throws exception on concurrent modificationConcurrent collections (e.g., CopyOnWriteArrayList) — allow safe modification from multiple threads without exceptions
while (it.hasNext()) manual loopstream().filter().collect() — functional pipeline that creates a new collection instead of modifying the original
collection.removeIf(predicate) — single conditionSpliterator<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

PROBLEM 1 — CONCEPTUAL
Explain in your own words why calling list.remove(element) inside a for-each loop causes a ConcurrentModificationException, but calling iterator.remove() does not.
PROBLEM 2 — BASIC CALCULATION
Given 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?
PROBLEM 3 — INTERMEDIATE
A student writes the following code and claims it works: 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.
PROBLEM 4 — APPLIED
You are building a to-do list app. The user has an 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.
PROBLEM 5 — CRITICAL THINKING
Consider a 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.

Varsity Tutors • Java • Using Iterator — Use Iterator to traverse and remove safely (intro)