If you've been writing Java for more than a week, you've written a for loop. But there's a good chance you're using it on autopilot without fully understanding what each part is doing — or when to reach for a different loop entirely.
This article breaks down the for loop properly, covers the variants Java gives you, and touches on the mistakes that even experienced developers make.
The Basic Structure
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
There are three parts inside the parentheses, separated by semicolons:
- Initialisation — int i = 0 — runs once before the loop starts
- Condition — i < 10 — checked before every iteration, loop stops when false
- Update — i++ — runs after every iteration
This seems obvious until you realise that all three parts are optional. This is valid Java:
for (;;) {
// infinite loop — runs forever
}
That's equivalent to while(true). Understanding that all three parts are optional makes you understand what the loop is actually doing — not just following a template.
Iterating Over Arrays
`
String[] languages = {"Java", "Python", "JavaScript"};
for (int i = 0; i < languages.length; i++) {
System.out.println(languages[i]);
}`
One thing developers trip over here: languages.length is a property, not a method call — no parentheses. Compare this to ArrayList where it's list.size(). Getting these mixed up is a surprisingly common bug.
The Enhanced for Loop (for-each)
Java 5 introduced the enhanced for loop, and for most iteration tasks it's cleaner:
for (String lang : languages) {
System.out.println(lang);
}
Read this as "for each String lang in languages". No index management, no off-by-one errors.
Use the enhanced for loop when:
- You don't need the index
- You're just reading elements, not modifying them
- You're iterating over any Iterable — arrays, ArrayLists, Sets, etc.
Use the traditional for loop when:
- You need the index value
- You're modifying elements by position
- You need to iterate in reverse
- You need to skip elements (step by 2, etc.)
Nested for Loops
`int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int row = 0; row < matrix.length; row++) {
for (int col = 0; col < matrix[row].length; col++) {
System.out.print(matrix[row][col] + " ");
}
System.out.println();
}
`
The outer loop walks rows. The inner loop walks columns. Use row and col instead of i and j — it makes the intent obvious at a glance.
Variable Scope Inside the Loop
A variable declared in the initialisation section only exists inside the loop:
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
// System.out.println(i); — won't compile, i is out of scope
If you need the final value after the loop, declare it outside:
int i;
for (i = 0; i < 5; i++) {
System.out.println(i);
}
System.out.println("Final i: " + i); // prints 5
break and continue
break exits the loop immediately:
for (int i = 0; i < 10; i++) {
if (i == 5) break;
System.out.println(i); // prints 0 to 4
}
continue skips the rest of the current iteration:
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue;
System.out.println(i); // prints odd numbers only
}
In nested loops, break only exits the innermost loop. For breaking out of multiple levels, Java supports labelled breaks:
outer:
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (j == 2) break outer;
System.out.println(i + ", " + j);
}
}
Common Mistakes
Off-by-one errors are the most frequent bug. The condition i <= 10 runs 11 times. i < 10 runs 10 times. For zero-indexed arrays always use i < array.length.
Modifying a collection while iterating with a for-each loop throws ConcurrentModificationException:
`
List list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
if (s.equals("b")) list.remove(s); // throws exception
}`
Use an Iterator directly or iterate backwards with a traditional for loop when removing while iterating.
When Not to Use a for Loop
If you're transforming or filtering a collection in modern Java, streams are often cleaner:
// Traditional
List<Integer> evens = new ArrayList<>();
for (int n : numbers) {
if (n % 2 == 0) evens.add(n);
}
// Stream equivalent
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
Neither is universally better — traditional loops are easier to debug step by step, streams shine for complex pipelines.
Wrapping Up
The for loop is one of those things that looks simple on the surface but has enough depth to trip up developers at every level. Get comfortable with all three variants — traditional, enhanced, and the edge cases around scope and modification — and you'll write cleaner, more intentional Java.
For a deeper walkthrough of for loops with more examples, is worth a read.
Top comments (0)