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.

Java programming emblem with a coffee cup, code braces, and terminal
Inside this lesson 10 sections 5 questions 4 figures

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

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.

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.

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.

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.

Iterator Cursor Movement Through an ArrayListState 1: Initial"Apple""Bat""Cat""Dog"▼cursorState 2: After next()"Apple""Bat""Cat""Dog"cursorreturns "Apple"State 3: After next()"Apple""Bat""Cat""Dog"cursorreturns "Bat"State 4: remove()"Apple""Bat"REMOVED"Cat""Dog"State 5: After shift"Apple""Cat""Dog"cursorList intact, no crash!The cursor adjusts automatically after remove(), so no elements are skipped.
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.

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

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.

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.

Three Approaches to Removing Elements During Iteration❌ For-Each + list.remove()for (String s : list) { if (s.length() < 4) { list.remove(s); }}// Throws// ConcurrentModification// Exception!CRASH at runtimemodCount ≠ expectedModCount⚠️ Index For-Loopfor (int i = 0; i < list.size(); i++) { if (list.get(i) .length() < 4) { list.remove(i); i--; // fix index }}Works, but fragileEasy to forget i-- and skip items✅ Iterator.remove()Iterator<String> it = list.iterator();while (it.hasNext()) { String s = it.next(); if (s.length() < 4) { it.remove(); }}Safe and cleanWorks with any Collection typeAlways prefer the Iterator pattern (right) when you need to remove elements during traversal.
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.

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. 1

    Step 1 — Create the List

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

    Step 2 — Obtain an Iterator

    Call Iterator<String> it = names.iterator(); to get an Iterator. The cursor is positioned before "Alice".
    Cursor: before index 0
  3. 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. 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. 5

    Step 5 — Verify the Result

    Print 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] ✓

SECTION 7

Strengths & Limitations of Iterator

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.

Comparison of element-removal approaches in Java

💡

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).

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 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

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

Explain in your own words why calling list.remove(element) inside a for-each loop causes a ConcurrentModificationException, but calling iterator.remove() does not.
Show answer
The for-each loop uses a hidden Iterator behind the scenes. When you call 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

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?
Show answer
The code would be: 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

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.
Show answer
The code throws an 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

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.
Show answer
The method would obtain an Iterator and loop through the list, checking each task: 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

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?
Show answer
You cannot use an index-based for-loop with a HashSet because Sets do not have indices — there is no 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.

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