How does the for-each loop iterate over an array versus an Iterable, and what does the compiler generate for each?
answer
- Two expansions: array=index, Iterable=iterator()
- Arrays allocate no Iterator
- Only collections throw CME (fail-fast)
- null source: NPE at .length vs .iterator()
- Pure syntactic sugar, no special bytecode
basics
~10 sFor an array the compiler turns it into an index-based loop using array.length. For a collection it calls iterator() and uses hasNext()/next(). The source code looks the same, but the generated code differs.
solid answer
~50 sThe for-each loop is pure syntactic sugar — the compiler expands it into one of two forms depending on the source type. For an array, it generates a hidden indexed loop: it computes the length once, walks an int index from 0, and assigns array[i] to your variable each pass. For anything implementing Iterable, it calls source.iterator() once, then loops while it.hasNext() is true, assigning it.next() to your variable. So arrays never create an Iterator object, while collections do. This explains several behaviours: arrays can't throw ConcurrentModificationException (no iterator tracking modifications), the array length is snapshotted once, and a null source NPEs differently (array: at the length access; Iterable: at the iterator() call). The element type after the colon must be assignable from the source's element type, and unboxing applies for primitive variables over a collection of wrappers.
code
java · 17 lines// Source you write:
for (String s : list) {
System.out.println(s);
}
// Roughly what the compiler generates for an Iterable:
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
// And for an array, it.length / indexing instead:
String[] a = arr;
for (int i = 0, n = a.length; i < n; i++) {
String s = a[i];
System.out.println(s);
}go deeper
Knows for-each works on both arrays and collections and produces the same elements.
Can describe both compiler expansions (index loop vs iterator()/hasNext()/next()) and that arrays allocate no iterator.
Connects the expansions to observable behaviour: CME only on fail-fast collections, allocation cost, and where null sources NPE.
Reasons about iterator allocation in hot paths, JIT/escape-analysis implications, and chooses array vs collection traversal with performance and correctness contracts in mind.
## The key idea: it's syntactic sugar **Syntactic sugar** means a convenient notation that the compiler rewrites into more basic constructs — there is no special bytecode for for-each. The Java Language Specification defines **two distinct expansions** chosen by the static type of the thing after the colon. ## Case 1: the source is an array Given: ```java for (String s : arr) { use(s); } ``` the compiler produces roughly: ```java String[] a = arr; // evaluate the source once int len = a.length; // length read once, up front for (int i = 0; i < len; i++) { String s = a[i]; use(s); } ``` Consequences: **no `Iterator` object is created** (zero allocation), the length is captured once at the start, and indexing is direct. Because nothing is tracking structural changes, you will **never** get a `ConcurrentModificationException` from an array for-each. ## Case 2: the source is an Iterable `Iterable<T>` is the interface with a single method `iterator()` returning an `Iterator<T>`. An **Iterator** has `boolean hasNext()` and `T next()`. Given: ```java for (String s : list) { use(s); } ``` the compiler produces roughly: ```java Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); use(s); } ``` Consequences: an **Iterator object is allocated**; the loop is driven by `hasNext()`/`next()`; and many collection iterators are **fail-fast** — they hold a modification counter and throw `ConcurrentModificationException` if the collection is changed structurally mid-loop (other than via the iterator's own `remove()`, which for-each cannot call because the iterator is hidden). ## Observable differences | Aspect | Array | Iterable | |---|---|---| | Iterator allocated? | No | Yes | | Can throw CME? | No | Yes (fail-fast collections) | | NPE on null source | at `.length` | at `.iterator()` | | Length/size evaluated | once, up front | implicitly via `hasNext()` each step | ## Type rules The loop variable's type must be **assignable from** the element type. Over a `List<Integer>` you may declare `int n` — the compiler inserts **unboxing** (converting the `Integer` wrapper to a primitive `int`), which **NPEs** if an element is null. You may also widen, e.g. iterate a `List<String>` as `Object o`. ## Why it matters Knowing the two expansions explains real behaviour you'll debug: phantom CMEs only on collections, allocation pressure from millions of iterator objects in hot loops over collections, and where exactly a null source blows up.
- Why can a for-each over a List throw ConcurrentModificationException but one over an array cannot?The List form uses a fail-fast Iterator that tracks a modCount; structural changes during the loop trip it. The array form is a plain indexed loop with no iterator and no modification tracking.
- If the source expression is null, where does the NullPointerException occur for each case?For an array, when the compiler reads its .length; for an Iterable, when it calls .iterator(). Both throw NPE, just at different generated steps.
saying these in an interview costs you the question
- Claiming for-each over an array creates an Iterator
- Thinking the compiler emits special foreach bytecode rather than desugaring
- Assuming arrays can throw ConcurrentModificationException