Loops and Iteration
Repeating work in Java: for, enhanced for, while and do-while loops, break and continue with labels, and iterating safely over collections.
A loop repeats a block of code while a condition holds, or once for
each element of a collection. The general idea, and the difference
between
pre-test
and
post-test
loops, is explained under
iteration. The examples on this page are compact source files with
void main() and IO.println (see
your first program).
while and do-while
A while loop tests its boolean condition
before every round and stops as soon as it is false. It suits
situations where you do not know in advance how many rounds are
needed, such as retrying until something succeeds. Make sure
something in the body eventually makes the condition false;
otherwise the program hangs in an
infinite loop.
void main() {
int attempts = 0;
boolean connected = false;
while (!connected && attempts < 5) {
attempts++;
IO.println("Attempt " + attempts + " to reach the meter gateway");
connected = attempts == 3; // in this demo, the third attempt succeeds
}
IO.println(connected ? "Connected" : "Gave up");
}
Attempt 1 to reach the meter gateway Attempt 2 to reach the meter gateway Attempt 3 to reach the meter gateway Connected
The limit of five attempts is a safety net: even if the gateway never answers, the loop ends. That habit matters in real operations code, where an endless retry loop can hammer a system that is already struggling.
do-while
A do…while loop tests its condition
after each round, so the body always runs at least once.
That fits input validation: you have to ask before you can check the
answer. Note the semicolon after the closing while (…).
void main() {
int kwh;
do {
kwh = Integer.parseInt(IO.readln("Meter reading in kWh: "));
if (kwh < 0) {
IO.println("A reading cannot be negative.");
}
} while (kwh < 0);
IO.println("Recorded " + kwh + " kWh");
}
Meter reading in kWh: A reading cannot be negative. Meter reading in kWh: Recorded 412 kWh
The program was given -20 and then 412 as
input. IO.readln prints its prompt without a line
break, and typed input that comes from a file or pipe is not shown,
which is why each message follows the prompt on the same line. In a
terminal you would see what you type after the prompt.
The classic for loop
The classic for loop puts the three parts of a counting
loop in one line: an initialization that runs once, a condition
tested before every round, and an update that runs after every
round. It is the tool for working with positions (indexes), for
counting with a step, or for walking two arrays side by side.
void main() {
int[] monthlyKwh = { 412, 388, 395, 351 };
String[] months = { "January", "February", "March", "April" };
int total = 0;
for (int i = 0; i < monthlyKwh.length; i++) {
IO.println(months[i] + ": " + monthlyKwh[i] + " kWh");
total += monthlyKwh[i];
}
IO.println("Total: " + total + " kWh");
for (int minutes = 60; minutes > 0; minutes -= 20) {
IO.println("Maintenance window closes in " + minutes + " minutes");
}
}
January: 412 kWh February: 388 kWh March: 395 kWh April: 351 kWh Total: 1546 kWh Maintenance window closes in 60 minutes Maintenance window closes in 40 minutes Maintenance window closes in 20 minutes
Indexes in Java start at 0, so the last valid index of an array is
length - 1. Writing <= instead of
< is the classic off-by-one error; Java notices it
at run time:
void main() {
int[] monthlyKwh = { 412, 388, 395, 351 };
int total = 0;
for (int i = 0; i <= monthlyKwh.length; i++) {
total += monthlyKwh[i];
}
IO.println("Total: " + total);
}
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4
A variable declared in the initialization part, such as
i, exists only inside the loop. Using it after the loop
is a compile error (cannot find symbol); declare it
before the loop if you need its final value. Arrays themselves are
covered on
arrays and collections.
The enhanced for loop
Most loops do not need an index: they simply visit every element.
The
enhanced for loop (often
called the for-each loop) does exactly that:
for (Type name : source) runs the body once for each
element of source, which can be an array or anything
that is Iterable, such as a List or a
Set. Read the colon as "in".
void main() {
List<String> openTickets = List.of("INC-10041", "INC-10042", "INC-10045");
for (String ticket : openTickets) {
IO.println("Reminder sent for " + ticket);
}
double[] temperatures = { 18.5, 21.0, 19.5, 21.0 };
double sum = 0;
for (double t : temperatures) {
sum += t;
}
IO.println("Average: " + sum / temperatures.length + " °C");
}
Reminder sent for INC-10041 Reminder sent for INC-10042 Reminder sent for INC-10045 Average: 20.0 °C
There is no index and no condition to get wrong, which is why the enhanced for loop is the default choice. Use the classic form only when you really need the position, need to walk backwards or in steps, or need to replace elements of an array.
The loop variable is a copy
The loop variable receives a copy of each element's value. Assigning a new value to it changes only that copy, never the array or list:
void main() {
int[] readings = { 412, -1, 395 };
for (int reading : readings) {
if (reading < 0) {
reading = 0; // changes the copy only
}
}
IO.println(Arrays.toString(readings));
for (int i = 0; i < readings.length; i++) {
if (readings[i] < 0) {
readings[i] = 0; // changes the array
}
}
IO.println(Arrays.toString(readings));
}
[412, -1, 395] [412, 0, 395]
When the elements are objects, the copy is a copy of the reference, so calling a method that changes the object does affect the object in the list. The same rule applies to method parameters, as explained under methods and parameters.
Iterating over a map
A Map is not Iterable itself. Loop over
one of its views instead: keySet(),
values() or entrySet(), which gives you
the key and the value together. A TreeMap keeps its
keys sorted, so the order of the output is predictable; a
HashMap makes no promise about order (see
maps).
void main() {
Map<String, Integer> usageByMeter = new TreeMap<>();
usageByMeter.put("E-002", 1500);
usageByMeter.put("E-001", 350);
usageByMeter.put("G-007", 12);
for (Map.Entry<String, Integer> entry : usageByMeter.entrySet()) {
IO.println(entry.getKey() + " used " + entry.getValue());
}
}
E-001 used 350 E-002 used 1500 G-007 used 12
for (var entry : usageByMeter.entrySet()) is a shorter
way to write the same loop; var lets the compiler infer
the long type (see
variables, types and operators).
break and continue
break leaves the loop immediately; the program
continues after it. continue skips the rest of the
current round and starts the next one (in a for loop,
after running the update). Both are useful, but a loop with many of
them becomes hard to follow, so use them for clear exits such as
"found it" or "skip this one".
void main() {
List<String> queue = List.of(
"INC-10041 P3 resolved",
"INC-10042 P2 open",
"INC-10043 P1 open",
"INC-10044 P1 open");
for (String line : queue) {
if (line.endsWith("resolved")) {
continue; // nothing to do for this one
}
IO.println("Checking " + line);
if (line.contains(" P1 ")) {
IO.println("First P1 found: " + line.substring(0, 9));
break; // stop searching
}
}
}
Checking INC-10042 P2 open Checking INC-10043 P1 open First P1 found: INC-10043
The loop stopped at the first P1 ticket; INC-10044 was never
checked. A while (true) loop with a
break inside is a common pattern when the exit
condition is easiest to test in the middle of the body.
Labeled break and continue
In nested loops, a plain break or
continue only affects the innermost loop. To leave the
outer loop as well, give it a label (a name followed by a colon) and
write break label;. This
labeled break jumps out of the
labeled loop in one step; continue label; likewise
starts the next round of the outer loop. The concept is the same as
JavaScript's
labeled statements.
void main() {
String[] substations = { "Delft", "Gouda", "Leiden" };
double[][] transformerTemps = {
{ 61.0, 64.5 },
{ 58.0, 91.5, 63.0 },
{ 95.0, 60.0 }
};
search:
for (int s = 0; s < substations.length; s++) {
for (int t = 0; t < transformerTemps[s].length; t++) {
if (transformerTemps[s][t] > 90) {
IO.println("Overheating: " + substations[s] + ", transformer " + (t + 1));
break search;
}
}
IO.println(substations[s] + " is fine");
}
}
Delft is fine Overheating: Gouda, transformer 2
Leiden is never inspected and "Gouda is fine" is never printed:
break search left both loops at once. Without the
label, only the inner loop would stop, and the outer loop would
carry on. If you find yourself reaching for labels often, moving the
nested loops into a separate method and using return is
usually easier to read.
Iterators
Behind every enhanced for loop over a collection is an
iterator: an object that walks
through a collection one element at a time. Its
hasNext() method says whether another element is left,
next() returns it and moves on, and the optional
remove() deletes the element that next()
returned last. Every class that implements
Iterable (the Java form of an
iterable) can hand out
an iterator, which is all the enhanced for loop needs.
void main() {
List<String> team = List.of("Amara", "Mei", "Hamid");
Iterator<String> it = team.iterator();
while (it.hasNext()) {
String name = it.next();
IO.println("On shift: " + name);
}
}
On shift: Amara On shift: Mei On shift: Hamid
The compiler turns for (String name : team) into almost
exactly this code. You rarely write it by hand, except when you need
remove(), as in the next section.
Changing a collection while you loop
Removing or adding elements to a list while an enhanced for loop
runs over it breaks the iterator's bookkeeping. Java's standard
collections detect this and throw a
ConcurrentModificationException, despite the name usually in a program with a single thread. The
check happens in next(), so the exception appears at
the start of the round after the change.
void main() {
List<String> tickets = new ArrayList<>(
List.of("INC-10041 resolved", "INC-10042 open", "INC-10043 open"));
for (String ticket : tickets) {
if (ticket.endsWith("resolved")) {
tickets.remove(ticket);
}
}
IO.println(tickets);
}
Exception in thread "main" java.util.ConcurrentModificationException
The check is "best effort", and that makes the bug sneaky. When the element you remove is the second-to-last one, the loop simply ends without an exception, and the last element is never visited:
void main() {
List<String> team = new ArrayList<>(List.of("Amara", "Mei", "Hamid"));
for (String name : team) {
IO.println("Visiting " + name);
if (name.equals("Mei")) {
team.remove(name);
}
}
IO.println(team);
}
Visiting Amara Visiting Mei [Amara, Hamid]
After the removal the list has two elements and the iterator has
already handed out two, so hasNext() answers false.
Hamid is still in the list but was skipped. A test with a different
list would throw instead, which is why such code can pass testing
and fail later.
Removing safely
There are two safe ways. removeIf takes a condition (a
lambda)
and removes every matching element in one call. When you need more
logic per element, use an explicit iterator and its
remove() method, which keeps the bookkeeping right.
void main() {
List<String> tickets = new ArrayList<>(
List.of("INC-10041 resolved", "INC-10042 open", "INC-10043 resolved"));
tickets.removeIf(ticket -> ticket.endsWith("resolved"));
IO.println(tickets);
List<String> team = new ArrayList<>(List.of("Amara", "Mei", "Hamid"));
Iterator<String> it = team.iterator();
while (it.hasNext()) {
String name = it.next();
if (name.startsWith("M")) {
it.remove();
IO.println("Removed " + name);
}
}
IO.println(team);
}
[INC-10042 open] Removed Mei [Amara, Hamid]
Unnamed variables
Sometimes a loop or a catch needs a variable that you
never use, for instance when you only count elements. Since Java 22
you can write an underscore instead of a name. An
unnamed variable
_ tells both the reader and the compiler that the value
is deliberately ignored.
void main() {
List<String> rawReadings = List.of("412", "38x", "395", "", "351");
int lines = 0;
for (String _ : rawReadings) {
lines++;
}
int valid = 0;
int total = 0;
for (String raw : rawReadings) {
try {
total += Integer.parseInt(raw);
valid++;
} catch (NumberFormatException _) {
// skip readings that are not numbers
}
}
IO.println(valid + " of " + lines + " readings valid, total " + total + " kWh");
}
3 of 5 readings valid, total 1158 kWh
The try and catch blocks are explained on
exceptions and error handling. Several unnamed variables may appear in the same scope, because
none of them can be referred to.
Loops and streams
Modern Java code often replaces simple loops with a method call. A
collection's forEach method runs a lambda for every
element, IntStream.rangeClosed(1, 12) produces the
numbers 1 to 12, and a stream pipeline can filter, transform and add
up in one expression.
void main() {
List<Integer> monthlyKwh = List.of(412, 388, 395, 351, 298, 260);
int loopTotal = 0;
for (int kwh : monthlyKwh) {
if (kwh > 300) {
loopTotal += kwh;
}
}
int streamTotal = monthlyKwh.stream()
.filter(kwh -> kwh > 300)
.mapToInt(Integer::intValue)
.sum();
IO.println(loopTotal + " " + streamTotal);
IO.println(IntStream.rangeClosed(1, 5).sum());
}
1546 1546 15
Both versions give the same answer; choose the one that reads best.
Streams shine when there are several steps (filter, map, group,
sort); a plain loop is clearer when the body has side effects, needs
break, or changes several variables. Streams are
covered on
lambdas, functional interfaces and streams.
Choosing a loop
| Loop | Use it when |
|---|---|
for (T x : items) |
you visit every element of an array or collection (the default) |
for (int i = 0; i < n; i++) |
you need the index, a step, a reverse order, or you change array elements |
while (condition) |
the number of rounds is unknown: retries, reading until the input ends |
do { … } while (condition); |
the body must run at least once, as with asking for input |
explicit Iterator |
you remove elements while walking through a collection |
removeIf, forEach, streams |
one clear operation on a whole collection: remove, print, filter, sum |
Whatever the loop, think about how much work it does as the data grows: a loop inside a loop over the same list grows with the square of its size (see nested loops over the same data).