Skip to content
Dev Dump

Collection Iteration

Java provides several ways to iterate over collections. Each has its own strengths — pick the one that fits your use case.

MethodBest ForCan Remove?Functional?
Enhanced for-loopSimple iterationNoNo
IteratorSafe removal during iterationYesNo
ListIteratorBidirectional traversal + modificationYes (+ add/set)No
forEachClean one-liners with lambdasNoPartially
Stream APIFilter/map/reduce pipelinesNoYes
Index-based forWhen you need the indexManualNo

Syntactic sugar over Iterator. Cleanest syntax for simple read-only traversal.

for (String fruit : fruits) {
System.out.println(fruit);
}
// Works with arrays, Lists, Sets — anything Iterable
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + ": " + e.getValue());
}

Use when you need to remove elements safely during iteration.

Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
if (shouldRemove(item)) {
it.remove(); // safe — this is the only way to remove during iteration
}
}

ConcurrentModificationException — the most common pitfall:

// WRONG — throws ConcurrentModificationException
for (String item : list) {
if (item.equals("x")) list.remove(item);
}
// RIGHT — use Iterator.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("x")) it.remove();
}

Extends Iterator for List types. Supports bidirectional traversal and in-place modification.

ListIterator<String> it = list.listIterator();
// Forward
while (it.hasNext()) {
String item = it.next();
if (item.equals("old")) it.set("new"); // replace current
}
// Backward
while (it.hasPrevious()) {
System.out.println(it.previous());
}
// Insert after current position
it.add("inserted");

Concise for simple operations. Available on all Iterable types and on Map.

list.forEach(System.out::println);
list.forEach(item -> process(item));
// Map has its own two-arg forEach
map.forEach((key, value) -> System.out.println(key + "=" + value));

Best for complex processing pipelines — filter, transform, aggregate.

List<String> result = names.stream()
.filter(name -> name.length() > 4)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
long count = names.stream()
.filter(n -> n.startsWith("A"))
.count();
Optional<String> first = names.stream()
.filter(n -> n.length() > 4)
.findFirst();
// Parallel processing (use with care)
names.parallelStream().forEach(System.out::println);

Only efficient for random-access lists like ArrayList. Avoid on LinkedList (each get(i) is O(n)).

for (int i = 0; i < list.size(); i++) {
System.out.println(i + ": " + list.get(i));
}
// Useful when you need the index or need to skip elements
for (int i = list.size() - 1; i >= 0; i--) {
if (shouldRemove(list.get(i))) list.remove(i);
}

This is why ConcurrentModificationException happens — and when it doesn’t.

Most collection iterators (ArrayList, HashMap, HashSet, etc.) are fail-fast. They track a modification count (modCount) internally. If the collection is modified structurally (add/remove) after the iterator is created — by anything other than the iterator itself — it immediately throws ConcurrentModificationException.

List<String> list = new ArrayList<>(List.of("a", "b", "c"));
Iterator<String> it = list.iterator();
list.add("d"); // modCount changes
it.next(); // throws ConcurrentModificationException

This is a best-effort detection — it’s not guaranteed in concurrent scenarios, but it catches most bugs fast.

Iterators from java.util.concurrent collections (ConcurrentHashMap, CopyOnWriteArrayList, etc.) are fail-safe. They never throw ConcurrentModificationException because they work on a separate copy or a snapshot of the data.

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(List.of("a", "b", "c"));
Iterator<String> it = list.iterator();
list.add("d"); // no problem — iterator works on a snapshot
it.next(); // "a" — from the original snapshot

Trade-off: fail-safe iterators may not reflect the latest modifications.

Fail-FastFail-Safe
CollectionsArrayList, HashMap, HashSet, etc.ConcurrentHashMap, CopyOnWriteArrayList
On modificationThrows ConcurrentModificationExceptionKeeps going (snapshot)
PerformanceNo overheadCopy/snapshot overhead
Reflects changes?N/A (it throws)No — works on stale data
  1. Default to enhanced for-loop — simplest and readable
  2. Need to remove? — use Iterator
  3. Need index? — index-based loop (only on ArrayList)
  4. Processing pipeline? — Stream API
  5. Never modify a collection inside an enhanced for-loop
  6. Multi-threaded iteration? — use concurrent collections (fail-safe iterators)