Command Palette

Search for a command to run...

PHASE 2Beginner Java 5+ ~30 min· topic 7 of 10

Topic 2.7

The Enhanced for Loop

In one line

for (Type item : collection) visits every element of an array or any Iterable (like a List) in order, with no index to get wrong. The loop variable is a copy of each element, and you must not add to or remove from a collection while walking it this way.

Think of it like this

A teacher handing back exam papers. She doesn't count 'paper number 0, paper number 1...'; she just takes the next paper from the pile until the pile is empty. The enhanced for loop works like that: 'for each paper in the pile, do this'. You get each item, but not its position number.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

for-each loop
Another name for the enhanced for loop: do something for each element.
Element
One item stored in an array or collection.
Iterable
A Java interface for anything that can hand out its elements one by one. List and Set are Iterable.
Iterator
An object that walks through a collection: hasNext() asks 'is there another?', next() gives it.
Reference
The address of an object on the heap. Object variables hold references, not the objects themselves (Topic 3.11).
ConcurrentModificationException
The error an ArrayList (and most collections) throws when it's changed while an iterator is walking it.
Desugaring
The compiler rewriting a convenient piece of syntax into simpler code it already understands.

Step by step

01Same job, less to get wrong

Compare the two loops. The classic one has three places to make an off-by-one mistake (start, test, index). The for-each has none. When all you need is 'each element, in order', it's the safer choice.

Main.javawhole filejava
int[] scores = {72, 95, 88};

for (int i = 0; i < scores.length; i++) {   // classic
    System.out.println(scores[i]);
}

for (int score : scores) {                    // enhanced
    System.out.println(score);
}

02Arrays, lists, sets and maps

Anything Iterable works: a List visits elements in order, a TreeSet in sorted order. A Map isn't Iterable itself; loop over one of its views. entrySet() gives you key and value together, which avoids a second lookup per key.

You'll meet these collections properly in Phase 9; for now, notice the loop looks the same for all of them.

Main.javawhole filejava
List<String> fruits = List.of("apple", "banana", "cherry");
for (String f : fruits) {
    System.out.println(f);
}

Map<String, Integer> stock = new TreeMap<>(Map.of("pens", 12, "ink", 3));
for (Map.Entry<String, Integer> e : stock.entrySet()) {
    System.out.println(e.getKey() + " = " + e.getValue());
}

for (char c : "hey".toCharArray()) {     // a String is not Iterable
    System.out.print(c + "-");
}
terminal
$ java Main.java
── expected output ──
apple
banana
cherry
ink = 3
pens = 12
h-e-y-

03The loop variable is a copy

Each iteration, Java copies the current element into the loop variable. Assigning a new value to that variable changes the copy only. The array is untouched.

To change the array's contents, you need the index: for (int i = 0; i < a.length; i++) a[i] *= 2;.

Many teams write for (final int x : a) (or turn on a lint rule) so that accidentally assigning to the copy becomes a compile error.

Main.javawhole filejava
int[] a = {1, 2, 3};
for (int x : a) {
    x = x * 2;            // changes the copy only
}
System.out.println(Arrays.toString(a));   // [1, 2, 3]
The loop variable is a copydiagram
Rendering diagram…

04Objects: the reference is copied, not the object

When the elements are objects, the loop variable gets a copy of the reference: it points at the same object as the array slot. Calling a method that changes the object (sb.append("!")) changes the shared object, so the array 'sees' it.

Reassigning the variable (sb = new StringBuilder()) only points the copy somewhere else; the array slot still points at the old object. This is exactly Java's pass-by-value rule, which Topic 3.3 explains in depth.

05What the compiler turns it into

For an array, javac stores the array in a hidden variable, reads its length once, and runs an index loop. That's why the loop over a null array fails with 'Cannot read the array length'.

For an Iterable, javac calls iterator() and loops on hasNext()/next(). A null list fails with 'Cannot invoke "java.util.List.iterator()"'.

Desugared.javawhole filejava
// for (int s : scores) { body }  becomes roughly:
int[] arr$ = scores;
int len$ = arr$.length;          // NullPointerException here if scores is null
for (int i$ = 0; i$ < len$; i$++) {
    int s = arr$[i$];
    // body
}

// for (String name : names) { body }  becomes roughly:
for (Iterator<String> it$ = names.iterator(); it$.hasNext(); ) {
    String name = it$.next();
    // body
}

06Don't change a list while for-each walks it

An ArrayList keeps a counter, modCount, that goes up on every add or remove. Its iterator remembers the value when it starts. On each next(), if the counter has changed, it throws ConcurrentModificationException (fail-fast). The name is misleading: no threads are involved.

There's a nasty corner: removing the second-to-last element doesn't throw. After the removal, hasNext() compares the cursor with the new, smaller size, decides it's finished, and the loop quietly skips the last element. Fail-fast is a best-effort check, not a guarantee.

The safe ways: list.removeIf(x -> ...) (Java 8), or an explicit Iterator and it.remove(), which updates the iterator's own bookkeeping.

terminal
$ java Main
── expected output ──
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049)
at Main.main(Main.java:7)
The ArrayList line numbers come from JDK 21 and differ between JDK versions.

07var in for-each, and choosing the right loop

Since Java 10 you can write for (var name : names); the type is inferred from the element type (Topic 1.11). It helps with long types such as Map.Entry<String, List<Integer>>.

Pick for-each when you need each element once, in order, and don't need its position. Pick the classic for when you need the index, a different order or step, neighbours, or to modify array slots. Pick an explicit Iterator when you must remove while walking.

Try it yourself

  1. 1

    Trigger the exception

    In 'Removing safely', change "Bob" to "Ana" in the first loop. Predict what happens, then run. You now get ConcurrentModificationException, because the loop calls next() again after the removal.

  2. 2

    Prove the copy

    In 'The loop variable is a copy', add final to the first loop: for (final int x : numbers). Compile. The assignment x = x * 10 is now an error (cannot assign a value to final variable x), which is exactly the safety net teams want.

  3. 3

    Loop a map

    In 'Lists, sets and maps', add stock.put("glue", 7); and predict where it appears in the output. (A TreeMap keeps keys sorted, so glue comes first.)

Code & diagrams

Totals, maximum and counting with for-each New tab
Sign in to run this example in your browser.

Expected output

Total: 411
Average: 82.2
Best: 95
90 or above: 2
Vowels: 6
Lists, sets and maps New tab
Sign in to run this example in your browser.

Expected output

list: cherry
list: apple
list: banana
sorted set: apple
sorted set: banana
sorted set: cherry
ink = 3
paper = 500
pens = 12
The loop variable is a copy New tab
Sign in to run this example in your browser.

Expected output

After for-each: [1, 2, 3]
After index loop: [10, 20, 30]
Builders: [hi!, yo!]
Removing safely (and the silent skip) New tab
Sign in to run this example in your browser.

Expected output

visiting Ana
visiting Bob
Cy was never visited: [Ana, Cy]
Odds via iterator: [1, 3, 5]
Odds via removeIf: [1, 3, 5]

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Remove from a list inside for-each

Remove an element from an ArrayList while a for-each loop walks it.

Main.javawhole filejava
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("Ana", "Bob", "Cy", "Dee"));
        for (String name : names) {
            if (name.startsWith("A")) {
                names.remove(name);
            }
        }
    }
}
terminal
$ javac Main.java && java Main
── what you'll see ──
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049)
at Main.main(Main.java:7)

Break #2

Loop over a null array

Set int[] scores = null; and loop over it.

Main.javawhole filejava
public class Main {
    public static void main(String[] args) {
        int[] scores = null;
        for (int s : scores) {
            System.out.println(s);
        }
    }
}
terminal
$ javac Main.java && java Main
── what you'll see ──
Exception in thread "main" java.lang.NullPointerException: Cannot read the array length because "<local2>" is null
at Main.main(Main.java:4)

Myth vs fact

Myth

Assigning to the for-each variable updates the array.

Fact

The variable is a copy of the element. Use an index loop to change array slots.

Myth

The enhanced for loop is slower than an index loop.

Fact

Over arrays it compiles to an index loop. Over an ArrayList it uses an iterator, which the JIT optimises to about the same cost.

Myth

ConcurrentModificationException means two threads are involved.

Fact

It's usually one thread changing a collection while iterating over it.

Myth

If removing inside for-each didn't throw, the code is fine.

Fact

Removing the second-to-last element ends the loop early without an exception, silently skipping the last element.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    JLS 14.14.2 defines the translation precisely. For arrays the expression is evaluated once into a temporary, so reassigning the original array variable inside the loop doesn't affect the iteration. For Iterable, the declared loop variable is assigned from next() with an implicit cast, which is where heap pollution from raw types surfaces as ClassCastException.

  • ▸

    Iterating a LinkedList with an index loop calls get(i), which walks from the nearest end each time: O(n²) in total. For-each uses the iterator: O(n). Over an ArrayList both are O(n). Use for-each when you don't know the List implementation.

  • ▸

    modCount checking is best-effort: the ArrayList iterator only checks in next() (and remove()), and hasNext() compares cursor != size. Concurrent collections (CopyOnWriteArrayList, ConcurrentHashMap) instead give weakly consistent iterators that never throw CME.

  • ▸

    Escape analysis often removes the Iterator allocation entirely after inlining, so for-each over a collection usually creates no garbage in hot code.

Remember this

  1. 1

    The syntax is for (int score : scores), read aloud as 'for each score in scores'. Java runs the body once per element, in order, putting the current element into score each time. It's also called the for-each loop and arrived in Java 5.

  2. 2

    It works on two kinds of things: arrays, and any object that implements Iterable (every List, Set and Queue from the collections framework, Phase 9). For a Map, loop over map.entrySet(), keySet() or values(). A String isn't Iterable; loop over str.toCharArray() instead.

  3. 3

    There's no index, so it can't go out of bounds, but you also can't tell which position you're at, skip ahead, walk backwards, look at the neighbour arr[i + 1], or walk two arrays side by side. For those, use the classic for (Topic 2.6).

  4. 4

    The loop variable is a copy of the element. Assigning to it (score = 0) changes the copy, not the array. For objects, the copy is a copy of the reference (Topic 3.3), so calling a method that changes the object (sb.append("!")) does affect the element, while reassigning the variable doesn't.

  5. 5

    You must not change a collection's structure (add or remove elements) while for-each is walking it. ArrayList notices and throws ConcurrentModificationException. Use Iterator.remove(), removeIf (Java 8), or build a new list.

  6. 6

    The compiler rewrites the loop: over an array it becomes an index loop on a hidden copy of the array reference; over an Iterable it becomes Iterator it = list.iterator(); while (it.hasNext()) { T x = it.next(); ... }. Knowing this explains every rule above.

Explain it without notes

01

What can the enhanced for loop iterate over, and what does the compiler turn it into?

02

Why doesn't for (int x : arr) x = 0; clear the array, while for (StringBuilder sb : arr) sb.setLength(0); does clear every builder?

03

Why does removing from an ArrayList inside a for-each loop throw ConcurrentModificationException, and how should you remove instead?

04

When must you use a classic for loop instead of for-each?

Practice

01

Given String[] words = {"java", "loop", "enhanced", "for"}, use for-each to find the longest word and the total number of letters.

02

Count how many times the letter l appears in "hello world, hello loops" using for-each over the characters.

03

Given List<Integer> nums = List.of(3, 8, 1, 9, 4), use for-each to build a new ArrayList containing only the numbers greater than 3, then print it.

Trade-offs

  • ↔

    For-each removes index bugs and reads like English, but hides the position and can't modify slots or remove elements safely.

  • ↔

    For-each over a List works for every implementation in O(n); an index loop is O(n) on ArrayList but O(n²) on LinkedList.

  • ↔

    removeIf and streams (Phase 11) express filtering more directly than loops, but a plain for-each is easier to debug step by step.

Done when you can

  • Done when you can loop over arrays, lists, sets and map entries with for-each.

  • Done when you can explain why assigning to the loop variable doesn't change the array.

  • Done when you can write the desugared form of for-each over an array and over an Iterable.

  • Done when you can remove elements safely with Iterator.remove() or removeIf.

  • Done when you can say when a classic for loop is required instead.