☕ Java
Enhanced for Loop
The enhanced for loop — also called the for-each loop — was introduced in Java 5 to provide a clean, readable syntax for iterating over arrays and any class that implements Iterable. It eliminates index management and iterator boilerplate entirely. The developer declares what each element is and what to do with it, without concerning themselves with how the iteration mechanism works. This entry covers syntax, the Iterable contract, limitations compared to the indexed loop, modification restrictions, and how for-each maps to iterators under the hood.
Syntax and the Iterable Contract
The enhanced for loop header declares a loop variable (the element type and name) and an expression that evaluates to an array or an Iterable. For each element in the collection or array, the variable is bound to that element and the body executes. The loop exits after the last element has been processed.
The loop works with two categories of targets: arrays (any type) and objects whose class implements java.lang.Iterable<T>. The Iterable interface has a single method — iterator() — that returns an Iterator<T>. The Iterator provides hasNext() and next() to drive the loop. Every collection in the Java Collections Framework (List, Set, Queue, Deque, Map.keySet(), Map.values(), Map.entrySet()) implements Iterable, as does any custom class that provides iterator(). Implementing Iterable in your own class immediately enables it to work with the enhanced for loop.
Java
// ── Syntax ────────────────────────────────────────────────────────────
for (ElementType variable : arrayOrIterable) {
// body — variable is bound to each element in turn
}
// ── Iterating an array ────────────────────────────────────────────────
int[] numbers = {10, 20, 30, 40, 50};
for (int num : numbers) {
System.out.println(num); // 10 20 30 40 50
}
// ── Iterating a List ──────────────────────────────────────────────────
List<String> names = List.of("Alice", "Bob", "Carol");
for (String name : names) {
System.out.println("Hello, " + name);
}
// ── Iterating a Set ───────────────────────────────────────────────────
Set<String> roles = Set.of("USER", "ADMIN", "MANAGER");
for (String role : roles) {
System.out.println("Role: " + role);
// Order not guaranteed for HashSet
}
// ── Iterating Map entries ─────────────────────────────────────────────
Map<String, Integer> scores = Map.of(
"Alice", 95, "Bob", 87, "Carol", 92);
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.printf("%s: %d%n",
entry.getKey(), entry.getValue());
}
// ── What the compiler generates ──────────────────────────────────────
// The enhanced for loop over an Iterable compiles to:
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name = it.next();
// body
}
// The enhanced for loop over an array compiles to a standard index loop:
for (int i = 0; i < numbers.length; i++) {
int num = numbers[i];
// body
}Readability and Intent
The enhanced for loop's primary virtue is clarity of intent. An indexed for loop says "iterate from 0 to n-1 and access each element by index." An enhanced for loop says "do something for each element." The second is almost always the correct statement of intent when iterating a collection for reading or processing.
When reviewing code, an indexed for loop signals that the index itself matters — the position is being used for something other than element access. An enhanced for loop signals that only the elements matter. This convention carries information to the reader about the purpose of the loop, which is valuable documentation embedded in the code structure itself.
Java
// ── Readability comparison ───────────────────────────────────────────
// Indexed loop — index is only used to access the element
// (signal: "I need the index" — but index is not used for anything else)
List<String> fruits = List.of("Apple", "Banana", "Cherry");
for (int i = 0; i < fruits.size(); i++) {
System.out.println(fruits.get(i).toLowerCase());
}
// Enhanced for loop — cleaner, intent is clear
for (String fruit : fruits) {
System.out.println(fruit.toLowerCase());
}
// ── Processing collections ────────────────────────────────────────────
List<Order> orders = orderRepo.findAll();
// Compute total revenue
double totalRevenue = 0;
for (Order order : orders) {
totalRevenue += order.getTotal().doubleValue();
}
// Collect IDs
List<Long> orderIds = new ArrayList<>();
for (Order order : orders) {
orderIds.add(order.getId());
}
// Find first order over £100
Order largeOrder = null;
for (Order order : orders) {
if (order.getTotal().compareTo(new BigDecimal("100")) > 0) {
largeOrder = order;
break;
}
}
// ── 2D array — nested enhanced for ───────────────────────────────────
int[][] grid = {{1,2,3},{4,5,6},{7,8,9}};
for (int[] row : grid) {
for (int cell : row) {
System.out.printf("%3d", cell);
}
System.out.println();
}
// Output:
// 1 2 3
// 4 5 6
// 7 8 9Limitations — When NOT to Use Enhanced for
The enhanced for loop's simplicity comes from hiding the iteration mechanism. That hiding has costs: you cannot access the current index, you cannot iterate backwards, you cannot modify the collection size, and you cannot efficiently iterate multiple collections simultaneously. Any time you need these capabilities, the indexed loop or explicit Iterator is required.
The loop variable is a copy of the reference (for objects) or the value (for primitives). Assigning to it reassigns the copy, not the original array element. For primitives in arrays this means in-place modification is impossible through the enhanced for loop — a fact that surprises many developers.
Java
// ── Limitation 1: Cannot access the index ────────────────────────────
List<String> items = List.of("a", "b", "c");
// WRONG — no index available
for (String item : items) {
System.out.println(??? + ": " + item); // no index
}
// Use indexed loop when index matters:
for (int i = 0; i < items.size(); i++) {
System.out.println(i + ": " + items.get(i)); // 0:a 1:b 2:c
}
// ── Limitation 2: Cannot modify array elements ────────────────────────
int[] numbers = {1, 2, 3, 4, 5};
// WRONG — assigns to local copy, original array unchanged
for (int num : numbers) {
num *= 2; // modifies the copy, NOT numbers[i]
}
System.out.println(Arrays.toString(numbers)); // [1, 2, 3, 4, 5]
// Use indexed loop to modify in place:
for (int i = 0; i < numbers.length; i++) {
numbers[i] *= 2; // modifies the actual array element
}
System.out.println(Arrays.toString(numbers)); // [2, 4, 6, 8, 10]
// ── Limitation 3: Cannot remove from a List during iteration ─────────
List<String> names = new ArrayList<>(
List.of("Alice", "Bob", "Carol", "Dave"));
// WRONG — throws ConcurrentModificationException
for (String name : names) {
if (name.startsWith("C")) {
names.remove(name); // ← ConcurrentModificationException
}
}
// CORRECT — use Iterator.remove() or removeIf()
names.removeIf(name -> name.startsWith("C"));
// or
Iterator<String> it = names.iterator();
while (it.hasNext()) {
if (it.next().startsWith("C")) it.remove();
}
// ── Limitation 4: Cannot iterate in reverse ──────────────────────────
// Enhanced for always goes forward
// Use indexed loop for reverse:
for (int i = numbers.length - 1; i >= 0; i--) {
System.out.print(numbers[i] + " ");
}
// ── Limitation 5: Cannot iterate two collections simultaneously ───────
// Use indexed loop or zip them first:
List<String> keys = List.of("a", "b", "c");
List<Integer> vals = List.of(1, 2, 3);
for (int i = 0; i < keys.size(); i++) {
System.out.println(keys.get(i) + "=" + vals.get(i));
}Custom Iterable Classes
Any class that implements Iterable<T> immediately works with the enhanced for loop. This is one of Java's most elegant extensibility points — a single method implementation gives the class the full syntactic support of the language's iteration facility. Custom Iterable is useful for domain-specific collections, range generators, lazy sequences, and any object that represents a traversable structure.
Implementing Iterable correctly requires implementing hasNext(), next(), and optionally remove() on the returned Iterator. The Iterator must maintain its own state — typically a current position — so each call to iterator() returns a fresh, independent Iterator.
Java
// ── Custom range Iterable ─────────────────────────────────────────────
public class Range implements Iterable<Integer> {
private final int start;
private final int end;
private final int step;
public Range(int start, int end, int step) {
this.start = start;
this.end = end;
this.step = step;
}
public static Range of(int start, int end) {
return new Range(start, end, 1);
}
public static Range of(int start, int end, int step) {
return new Range(start, end, step);
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
private int current = start;
@Override
public boolean hasNext() {
return step > 0 ? current < end
: current > end;
}
@Override
public Integer next() {
if (!hasNext()) throw new NoSuchElementException();
int value = current;
current += step;
return value;
}
};
}
}
// ── Usage — works directly in enhanced for ────────────────────────────
for (int i : Range.of(0, 10)) {
System.out.print(i + " "); // 0 1 2 3 4 5 6 7 8 9
}
for (int i : Range.of(0, 20, 3)) {
System.out.print(i + " "); // 0 3 6 9 12 15 18
}
for (int i : Range.of(10, 0, -2)) {
System.out.print(i + " "); // 10 8 6 4 2
}
// ── Custom tree node Iterable (in-order traversal) ────────────────────
public class BinaryTree<T extends Comparable<T>>
implements Iterable<T> {
private Node<T> root;
@Override
public Iterator<T> iterator() {
// In-order traversal using a stack
return new Iterator<>() {
private final Deque<Node<T>> stack = new ArrayDeque<>();
{ pushLeft(root); } // initialise with leftmost path
private void pushLeft(Node<T> node) {
while (node != null) {
stack.push(node);
node = node.left;
}
}
@Override
public boolean hasNext() {
return !stack.isEmpty();
}
@Override
public T next() {
Node<T> node = stack.pop();
pushLeft(node.right);
return node.value;
}
};
}
}
// Usage:
BinaryTree<Integer> tree = buildTree();
for (int value : tree) {
System.out.print(value + " "); // sorted in-order output
}Enhanced for with Records, Streams, and Var
Modern Java features compose naturally with the enhanced for loop. Records make clean data carriers for iteration. The var keyword (Java 10+) infers the element type, reducing verbosity in deeply typed generics. Streams are not Iterable — they cannot be used directly in a for-each — but converting a stream to a collection or using a custom Iterable bridge enables stream-generated sequences to be consumed with the familiar for-each syntax.
Java
// ── var — type inference in enhanced for ────────────────────────────
// Java 10+: var infers the element type
List<Map.Entry<String, List<Order>>> entries =
new ArrayList<>(ordersByCustomer.entrySet());
// Without var — verbose type in loop variable
for (Map.Entry<String, List<Order>> entry : entries) {
String customer = entry.getKey();
List<Order> orders = entry.getValue();
}
// With var — cleaner
for (var entry : entries) {
var customer = entry.getKey(); // String
var orders = entry.getValue(); // List<Order>
System.out.println(customer + ": " + orders.size());
}
// ── Records as loop elements ──────────────────────────────────────────
record Point(int x, int y) {}
record LineSegment(Point start, Point end) {}
List<Point> polygon = List.of(
new Point(0, 0), new Point(3, 0),
new Point(3, 4), new Point(0, 4));
double perimeter = 0;
for (var point : polygon) {
System.out.println("Vertex: " + point);
}
// ── Stream is NOT Iterable — cannot use directly ──────────────────────
Stream<String> stream = names.stream().filter(n -> n.length() > 3);
// WRONG — Stream does not implement Iterable
// for (String name : stream) { ... } // compile error
// CORRECT option 1 — collect first
for (String name : stream.toList()) {
System.out.println(name);
}
// CORRECT option 2 — Iterable bridge (lazy, single-use)
Iterable<String> iterable = stream::iterator;
for (String name : iterable) {
System.out.println(name);
}
// ── Choosing between enhanced for and streams ──────────────────────────
// for-each: simple iteration, imperative logic, break/continue needed,
// side effects (e.g. accumulating into an external structure)
//
// Stream: pipeline transformations, map/filter/reduce,
// parallel processing, functional style, no break neededRelated Topics in Control Statements
Control Statements
Control statements determine the flow of execution in a Java program. Without them, code executes line by line from top to bottom. Control statements allow the program to make decisions, repeat actions, and jump to different parts of the code based on conditions. Java provides three categories: selection statements (if, if-else, switch), iteration statements (for, while, do-while), and jump statements (break, continue, return).
if Statement
The if statement is the most fundamental control flow construct in Java. It evaluates a boolean expression and executes a block of code only if the expression is true. If the condition is false the block is skipped entirely and execution continues with the next statement after the if block.
if-else Statement
The if-else statement extends the basic if by providing an alternative block that executes when the condition is false. It guarantees that exactly one of two blocks always executes — either the if block (condition true) or the else block (condition false). The if-else-if chain extends this to choose among more than two alternatives.
Nested if
A nested if is an if statement placed inside the body of another if or else block. Nesting allows multi-level decision making — first check a broad condition, then refine with a more specific condition inside it. While nesting is sometimes necessary, deep nesting quickly reduces readability and should be refactored using guard clauses, logical operators, or extracted methods.