How does the Java compiler translate an enhanced for-each loop, and what are the practical consequences of that translation?
answer
- iterator() called once, then hasNext()/next() loop
- Array form uses index + cached length
- Hidden iterator → can't call remove() → CME
- Loop variable is a copy each pass
- null source → NPE up front
basics
~10 sThe compiler rewrites for-each into a while loop that calls iterator() once, then loops on hasNext()/next(). Because you have no visible iterator handle, you cannot safely remove elements with the collection's remove() inside it.
solid answer
~40 sFor an Iterable, the compiler desugars `for (T x : coll)` into: obtain `Iterator<T> it = coll.iterator()` once, then `while (it.hasNext()) { T x = it.next(); ... }`. For an array it instead generates an index-counted loop using the cached length. Consequences: the iterator handle is hidden, so you cannot call `it.remove()` from a for-each — calling the collection's own `remove()` mid-loop instead triggers a `ConcurrentModificationException` from the fail-fast iterator. The loop variable is a fresh copy each pass, so reassigning it does not change the collection. iterator() is invoked exactly once, so a custom Iterable that returns a new iterator each call is fine. If you need to mutate during traversal, drop to an explicit Iterator and use its remove(), or use removeIf().
code
java · 14 lines// Wrong: structural change during for-each
List<String> list = new ArrayList<>(List.of("a", "", "b"));
for (String s : list) {
if (s.isEmpty()) list.remove(s); // throws ConcurrentModificationException
}
// Right: explicit iterator + it.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().isEmpty()) it.remove();
}
// Also right: bulk removeIf
list.removeIf(String::isEmpty);go deeper
Knows for-each is shorthand and that you shouldn't remove from a list while looping it.
Can write the desugared while-loop, explain the single iterator() call, and name removeIf / explicit iterator as the safe mutation routes.
Connects desugaring to the modCount fail-fast mechanism and CME, discusses the array-vs-Iterable forms and the copy semantics of the loop variable.
Reasons about API/lib design where Iterable single-shot-ness or expensive iterator() construction matters, and chooses traversal idioms for concurrent or large datasets accordingly.
## What 'desugaring' means 'Syntactic sugar' is a convenient surface syntax that the compiler mechanically rewrites into a more basic form before generating bytecode. The enhanced for loop (`for (T x : source)`), introduced in Java 5, is sugar: there is no special bytecode for it; the compiler expands it into ordinary loop constructs. ## The two desugaring forms The expansion depends on whether the source is an **array** or an **`Iterable`** (per the Java Language Specification, §14.14.2). **Iterable form.** For `for (T x : coll)` where `coll` is `Iterable<T>`: ```java for (Iterator<T> it = coll.iterator(); it.hasNext(); ) { T x = it.next(); // loop body } ``` Key points: `iterator()` is called **exactly once**, before the loop; each pass calls `hasNext()` then `next()`; and `x` is assigned a fresh local each iteration. **Array form.** For `for (T x : arr)` where `arr` is `T[]`: ```java T[] a = arr; // evaluated once for (int i = 0; i < a.length; i++) { T x = a[i]; // loop body } ``` The array reference and its length are captured once. Arrays do **not** implement `Iterable`; the compiler special-cases them. ## Practical consequence 1: you cannot remove via the iterator Because the desugared `it` is invisible to your code, you have **no handle** to call `it.remove()`. Developers often reach for the collection's own `remove(...)` inside a for-each instead: ```java for (String s : list) { if (s.isBlank()) list.remove(s); // BAD } ``` Most `java.util` collections return **fail-fast** iterators that track a `modCount` (modification counter). Structurally mutating the collection through any path other than `it.remove()` changes `modCount`, and the next `it.hasNext()`/`it.next()` detects the mismatch and throws `ConcurrentModificationException`. The fixes are: (a) use an explicit `Iterator` and call `it.remove()`; (b) use `Collection.removeIf(predicate)`; or (c) collect to-remove elements and remove after the loop. ## Practical consequence 2: the loop variable is a copy `x` is a fresh local rebound each pass. Reassigning `x` inside the body does nothing to the collection, and for primitives/immutables there is no way to write back through it. To replace elements you need a `ListIterator` and its `set(...)`, or index-based access. ## Practical consequence 3: iterator() runs once Since `iterator()` is invoked a single time, a custom `Iterable` whose `iterator()` is expensive pays that cost once per loop, not per element. It also means a 'single-shot' Iterable (one that can only be traversed once) breaks if the same object is reused in two for-each loops. ## Practical consequence 4: NPE on a null source The desugared code calls `source.iterator()` (or reads `source.length`) immediately, so a `null` source throws `NullPointerException` before the body runs — there is no implicit null-skip. ## Summary For-each is the cleanest way to read every element, but its hidden iterator means it is read-only with respect to structural change. Whenever you must add, remove, or replace during traversal, step down to an explicit `Iterator`/`ListIterator` or use bulk methods like `removeIf`.
- Why doesn't list.removeIf(...) throw ConcurrentModificationException?removeIf is implemented inside the collection (or via its iterator's remove), so the modCount and the iterator stay in sync — there is no external structural change racing against an active iterator.
- Can you modify an element's internal state (not the list structure) during a for-each?Yes — mutating a held object's fields is not a structural modification and does not change modCount, so it is safe. Only add/remove of elements is forbidden.
saying these in an interview costs you the question
- Claiming iterator() is called every loop pass — it is called once
- Believing you can call the hidden iterator's remove() from a for-each
- Thinking the loop variable writes back into the collection
- Assuming a null collection in for-each is silently skipped