Java For Loop

June 25, 20265 min readUpdated 8/20/2026

A loop runs a block repeatedly. Java has four, but the choice between them is nearly mechanical: if you are walking a collection, use for-each; if you are counting, use a classic for; if you do not know how many times, use while. This post covers all four and the two mistakes everyone makes at least once.

The classic for

for (int i = 0; i < 5; i++) {
    System.out.println(i);          // 0 1 2 3 4
}

Three parts, separated by semicolons, each doing one job:

  • int i = 0 — runs once, before anything else. i is scoped to the loop.
  • i < 5 — tested before every iteration. False means stop.
  • i++ — runs after every iteration.

Start at 0 and use <, not <=. That combination gives you exactly n iterations and matches how arrays and lists are indexed, which is why it is the convention. <= with a length is the off-by-one error below.

// Counting down, and stepping by more than one
for (int i = 5; i > 0; i--) {
    System.out.print(i + " ");      // 5 4 3 2 1
}

for (int i = 0; i < 10; i += 2) {
    System.out.print(i + " ");      // 0 2 4 6 8
}

For-each: the one you will use most

When you want every element and do not care about the index, for-each says so and cannot get the bookkeeping wrong:

List<String> names = List.of("Ana", "Bo", "Cy");

for (String name : names) {
    System.out.println(name);
}

int[] scores = {90, 85, 77};
int total = 0;
for (int score : scores) {
    total += score;
}
System.out.println(total);          // 252

Read the colon as "in": for each name in names. It works on arrays and on anything iterable, it has no counter to mis-initialise, and it cannot run off the end.

Its one limitation is that you have no index and cannot modify the collection. If you need either, use a classic for or an iterator.

while and do-while

while is for when the number of iterations is not known in advance:

int remaining = 5;
while (remaining > 0) {
    System.out.println(remaining);
    remaining--;                    // something MUST change the condition
}

// do-while always runs at least once, because the test is at the bottom
int attempts = 0;
do {
    attempts++;
} while (attempts < 3);
System.out.println(attempts);       // 3

The comment on that fourth line is the whole risk of while: if nothing inside the body moves the condition toward false, the loop never ends. When you write a while, find the line that will eventually stop it before you write anything else.

do-while is rare. Use it only when the body genuinely must run once regardless — prompting for input is the standard example.

break and continue

// break — stop the loop entirely
for (int i = 0; i < 10; i++) {
    if (i == 5) break;
    System.out.print(i + " ");      // 0 1 2 3 4
}

// continue — skip the rest of THIS iteration, carry on with the next
for (int i = 0; i < 10; i++) {
    if (i % 2 != 0) continue;
    System.out.print(i + " ");      // 0 2 4 6 8
}

break is how you write a search that stops as soon as it finds something, which is both faster and clearer than setting a flag and letting the loop run on:

List<String> names = List.of("Ana", "Bo", "Cy");

String found = null;
for (String name : names) {
    if (name.startsWith("B")) {
        found = name;
        break;                      // no reason to keep looking
    }
}
System.out.println(found);          // Bo

Nested loops

for (int row = 1; row <= 3; row++) {
    for (int col = 1; col <= 3; col++) {
        System.out.print(row * col + "\t");
    }
    System.out.println();
}

The inner loop completes fully for each single step of the outer one, so two nested loops over n items do work. That is fine for 3 rows and a problem for 100,000 records — nested loops over large collections are one of the most common causes of code that works in testing and crawls in production.

A plain break inside a nested loop exits only the inner one. A label exits both:

outer:
for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
        if (i * j > 6) break outer;     // leaves BOTH loops
        System.out.print(i * j + " ");
    }
}

Labels are legal and occasionally the clearest option, but they are rare enough that they surprise readers. Extracting the nested loop into a method and using return is usually better.

The two mistakes everybody makes

1. The off-by-one. <= where you meant <:

int[] numbers = {10, 20, 30};

for (int i = 0; i < numbers.length; i++) {
    System.out.println(numbers[i]);       // correct: indexes 0, 1, 2
}

// for (int i = 0; i <= numbers.length; i++) {
//     System.out.println(numbers[i]);    // ArrayIndexOutOfBoundsException at i == 3
// }

An array of length 3 has indexes 0, 1 and 2. There is no index 3. This is exactly why for-each is preferable when you do not need the index.

2. Modifying a collection while iterating it. This one does not fail politely either:

List<String> names = new ArrayList<>(List.of("Ana", "Bo", "Cy"));

// for (String name : names) {
//     if (name.startsWith("B")) names.remove(name);   // ConcurrentModificationException
// }

names.removeIf(name -> name.startsWith("B"));          // the right way
System.out.println(names);                             // [Ana, Cy]

The name of that exception is misleading — no second thread is involved. It simply means the collection changed while something was walking it. removeIf is the fix, and an Iterator with its own remove() is the fallback for anything more complicated.

When not to write a loop at all

A great many loops exist only to filter, transform or total a collection, and streams say that more directly:

List<String> names = List.of("Ana", "Bo", "Cy");

List<String> longNames = new ArrayList<>();
for (String name : names) {
    if (name.length() > 2) longNames.add(name.toUpperCase());
}

List<String> sameThing = names.stream()
        .filter(name -> name.length() > 2)
        .map(String::toUpperCase)
        .toList();

Neither is universally better. A loop is clearer when the body is long or does several things; a stream is clearer when you are describing a pipeline. Streams covers the trade properly.

Next

You have been looping over arrays without a proper introduction to them. Arrays is next.