When should you NOT use a for-each loop, and what should you use instead?
answer
- Need index/reverse/step -> classic for
- Need removal -> Iterator.remove() or removeIf
- Two collections in lockstep -> indexed loop
- Map key+value -> entrySet(), not keySet()+get
- Transformation -> Stream
basics
~20 sAvoid for-each when you need the index, want to go in reverse or skip elements, must remove items, or must walk two collections together. Use a classic indexed for loop, an explicit Iterator, or removeIf instead.
solid answer
~50 sFor-each is the right default for forward, read-only traversal, but it deliberately hides the index and the iterator, so it's the wrong tool when you need either. Reach for a classic indexed for loop when you need the position, want to iterate in reverse, use a step other than 1, modify elements by index, or traverse a backing array hot path where avoiding iterator allocation matters. Use an explicit Iterator when you must remove during traversal. Use removeIf for conditional deletion. Iterate entrySet() for maps when you need both key and value. When you need to walk two sequences in lockstep, for-each can't express that — use an indexed loop or zip them manually. Also prefer Streams when the operation is really a map/filter/reduce pipeline rather than a side-effecting traversal. The principle: pick the construct whose capabilities match the task; don't fight for-each's intentional limitations.
go deeper
Recognizes for-each can't give an index and can't safely remove; knows a classic for loop is the alternative.
Lists the main no-go cases (index, reverse, removal, parallel iteration) and the right replacement for each.
Chooses deliberately among for-each / indexed / Iterator / removeIf / Stream by matching capability to task, including map entrySet and allocation concerns.
Establishes idioms and review guidance, weighs readability vs allocation/perf, and knows when Streams genuinely improve intent over loops.
## Start from what for-each is good at The for-each loop is optimized for one job: **forward, read-only, single-source traversal**. It hides the **index** (position number) and the **iterator** (the walking object) to remove boilerplate and off-by-one bugs. Every limitation below is a direct consequence of hiding those two things, so the cure is to pick a construct that exposes what you need. ## Situations where for-each is the wrong tool **1. You need the index.** for-each gives you the element, never its position. If you must know 'this is the 3rd element' or write back via `array[i] = ...`, use a classic indexed loop: ```java for (int i = 0; i < a.length; i++) { a[i] = transform(a[i]); } ``` (Modifying the *contents* of an object you got from for-each is fine; what you can't do is replace the slot, because the loop variable is a copy of the reference/value.) **2. Reverse or non-unit step.** for-each is always front-to-back, one element at a time. For reverse order or `i += 2`, use an indexed loop (or `ListIterator` for reverse on a `List`). **3. Structural modification (add/remove).** As covered separately, mutating size during for-each throws `ConcurrentModificationException`. Use an explicit `Iterator.remove()` or `Collection.removeIf(predicate)`. **4. Parallel iteration of two collections.** for-each has one source. To advance two lists together, use an indexed loop over their common length, or two iterators advanced manually. **5. You need the key AND value of a Map.** A `Map` isn't `Iterable`. Don't loop `keySet()` and call `get(k)` (double lookup); iterate `entrySet()`: ```java for (Map.Entry<K,V> e : map.entrySet()) { use(e.getKey(), e.getValue()); } ``` **6. Hot-path allocation sensitivity.** Over a collection, for-each allocates an `Iterator` each loop. In extremely hot inner loops over an `ArrayList`, an indexed `get(i)` loop avoids that allocation. (Measure first — escape analysis often makes this moot.) **7. The work is a transformation pipeline.** If you're really filtering/mapping/reducing, a **Stream** (`list.stream().filter(...).map(...).collect(...)`) expresses intent better than a side-effecting for-each — though for simple side effects, for-each is clearer and faster than `forEach()` on a stream. ## The decision rule Default to for-each for plain reads. Switch only when the task needs a capability for-each intentionally omits: index, reverse/step, removal, multiple sources, or map entries. Choosing deliberately is a senior-level signal. ## Glossary - **Index**: integer position of an element. - **ListIterator**: a List-specific iterator that can also move backwards and set elements. - **entrySet()**: a Set view of a Map's key-value pairs.
- Can you reassign an element of a List or array via the for-each variable?No. The loop variable is a copy of the value/reference; assigning to it doesn't change the underlying slot. To replace elements you need the index (array[i]=... or list.set(i, ...)).
- Why iterate entrySet() instead of keySet() when you need values too?keySet() then get(key) does two hash lookups per element; entrySet() yields the key and value together in one pass, which is clearer and faster.
saying these in an interview costs you the question
- Using keySet()+get(k) to read map values instead of entrySet()
- Believing you can replace list/array slots through the loop variable
- Forcing for-each where an index or reverse traversal is genuinely needed
- Claiming for-each can iterate two collections at once