Back to Blog
Java

Java Iterator vs For-Each: When to Use Which

java iterator vs foreach: Compare Java Iterator and for-each loop: syntax, removal, fail-fast behavior, performance, and when to choose each for collection traversal.

JavaIteratorFor-Each LoopCollectionsFail-Fast
A visual comparison of a Java Iterator and a for-each loop, showing a collection being traversed with two distinct paths.

When you traverse a collection in Java, you typically reach for the enhanced for-each loop. It is concise and readable. But the underlying Iterator interface offers control that the for-each loop hides. Understanding the java iterator vs foreach distinction matters when you need to remove elements, work with custom iteration logic, or reason about concurrent modification failures.

The Enhanced For-Each Loop: Convenience and Constraints

The for-each loop, introduced in Java 5, is syntactic sugar over the Iterator interface. For any object that implements Iterable, the compiler translates the loop into an iterator-based traversal. Consider this example:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); for (String name : names) { System.out.println(name); }

The compiled code uses an Iterator internally, calling hasNext() and next() for each element. The loop hides the iterator variable, which is convenient but also removes access to it. You cannot call remove() or any other iterator method inside the loop. If you try to modify the collection structurally—by adding or removing elements—you will likely trigger a ConcurrentModificationException because the hidden iterator detects the modification.

The Iterator Interface: Explicit Control

When you need more than sequential read access, you can work with the Iterator directly. The interface defines three core methods: hasNext(), next(), and remove() (the latter is optional). Here is the same traversal using an explicit iterator:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); Iterator<String> it = names.iterator(); while (it.hasNext()) { String name = it.next(); System.out.println(name); }

This gives you a reference to the iterator. You can call remove() to delete the current element from the underlying collection, which is safe because the iterator updates the collection's modification count appropriately. The for-each loop cannot do this directly.

Removing Elements During Traversal

Removing an element while iterating is a common requirement. With the for-each loop, the following code throws ConcurrentModificationException:

List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie")); for (String name : names) { if (name.equals("Bob")) { names.remove(name); // throws ConcurrentModificationException } }

The exception occurs because the hidden iterator's next() method checks the collection's modCount against the expected value. When you call remove() on the collection directly, the modCount changes, and the iterator detects the mismatch on the next iteration.

Using the iterator's own remove() method avoids this:

List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie")); Iterator<String> it = names.iterator(); while (it.hasNext()) { String name = it.next(); if (name.equals("Bob")) { it.remove(); // safe } }

The iterator's remove() method updates both the collection and the iterator's internal state, so the next hasNext() call works correctly. This is the primary reason to choose an explicit iterator over a for-each loop.

Performance and Memory: Are They Different?

From a runtime perspective, the for-each loop and an explicit iterator are nearly identical. The compiler generates the same bytecode for both when iterating over an Iterable. The for-each loop does not introduce extra overhead beyond the iterator itself. If you need to access the index, the for-each loop is not suitable; you would use a traditional indexed for loop instead. However, that is a different comparison.

There is no meaningful performance difference between for (String s : list) and while (it.hasNext()) { String s = it.next(); }. The choice should be based on readability and required functionality, not on micro-optimizations. If you are iterating over an array, the for-each loop compiles to an indexed loop, but the same logic applies: use the construct that matches your intent.

Fail-Fast Behavior and Concurrent Modification

Both the for-each loop and an explicit iterator are fail-fast by default for most java.util collections. This means they throw ConcurrentModificationException if the collection is structurally modified after the iterator is created, except through the iterator's own remove() method. This behavior is designed to catch bugs early, but it is not guaranteed for all collection implementations. For example, CopyOnWriteArrayList uses a snapshot iterator that does not throw.

When you use a for-each loop, you cannot catch the modification because you do not have access to the iterator. With an explicit iterator, you can decide to use remove() safely or design your loop to handle the exception if you are working with a non-fail-fast collection. The key point is that the for-each loop gives you no control over the iterator's behavior.

Choosing Between Iterator and For-Each

Use the for-each loop when you only need to read elements and the iteration order is provided by the collection. It is the most readable and least error-prone. Use an explicit iterator when you need to:

  • Remove the current element during traversal.
  • Call methods on the iterator, such as forEachRemaining() (Java 8+).
  • Work with a custom Iterable that returns a specialized iterator.
  • Traverse a collection that does not support the enhanced for loop, such as a raw Iterator from a legacy API.

There is also ListIterator, which extends Iterator and adds bidirectional traversal and index access. If you need to move backward or insert elements, you need a ListIterator rather than a for-each loop.

Advanced Usage: Custom Iterators and Lazy Evaluation

The for-each loop works with any Iterable, but it always uses the default iterator. If you have a custom data structure that implements Iterable, you can control the iteration behavior by providing a custom Iterator. This is useful for lazy evaluation, where you generate elements on demand rather than materializing a full collection. For example, a custom iterator might read lines from a file or generate a sequence of numbers without storing them all in memory. In such cases, the for-each loop still works, but you may need to access the iterator directly to handle resource cleanup or to skip elements manually.

Consider a scenario where you want to skip the first few elements and then process the rest. With a for-each loop, you would need a boolean flag. With an explicit iterator, you can call next() a few times before entering the loop:

Iterator<Integer> it = someIterable.iterator(); for (int i = 0; i < 3 && it.hasNext(); i++) { it.next(); // skip } while (it.hasNext()) { process(it.next()); }

This level of control is not available in the for-each loop. The decision between iterator and for-each ultimately comes down to whether you need to manipulate the iteration process itself. For straightforward reads, the for-each loop is the better default. For any structural modification or custom traversal logic, the explicit iterator is the correct tool.

java iterator vs foreach: Practical Usage and Code Examples | RYUSLOG DEV