Topic 2.6
The for Loop
In one line
The classic for (init; condition; update) loop puts a loop's start, test and step on one line. It's the standard way to repeat a known number of times or walk through array indexes, and most loop bugs are off-by-one errors in its three parts.
Think of it like this
Climbing stairs to the 10th floor. You start at step 1, you keep going while you haven't passed step 10, and after each step you move up one. A for loop writes those three facts in one line: for (int step = 1; step <= 10; step++).
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Init
- The first part of a
forheader. It runs once, before the loop starts, usually to declare a counter. - Update
- The third part of a
forheader. It runs after every iteration, usuallyi++. - Counter
- A variable that counts iterations, conventionally named
i,jork. - Index
- A position in an array, starting at 0.
arr[0]is the first element. - Off-by-one error
- A loop that runs one time too many or too few, usually from
<=vs<or starting at 1 instead of 0. - Two pointers
- A technique that moves two indexes through an array, often from both ends towards the middle.
- Accumulator
- A variable that collects a result across iterations, like a running total.
Step by step
01The three parts of the header
for (int i = 1; i <= 5; i++) reads as: start i at 1; keep going while i is at most 5; after each pass, add 1 to i. The body runs with i = 1, 2, 3, 4, 5.
Write the three parts in this order every time and most bugs disappear: where do I start, when do I stop, how do I step.
for (int i = 1; i <= 5; i++) {
System.out.println("Step " + i);
}02The exact order of execution
The init runs once. Then it's always: test, body, update, test, body, update... The update runs after the body, and the test runs before the body. When the test fails, the loop ends without running the body or the update again.
So after for (int i = 0; i < 3; i++), the last value i reached was 3 (that's the value that failed the test), even though the body only saw 0, 1 and 2.
03for and while are the same machine
Every for is a while with its parts gathered on one line. The two snippets below compile to essentially the same bytecode.
The for version wins on two counts: the counter can't leak out (its scope is the loop), and you can see the start, stop and step in one glance.
// for
for (int i = 0; i < 3; i++) {
System.out.println(i);
}
// the same loop as a while
{
int i = 0;
while (i < 3) {
System.out.println(i);
i++;
}
}04Looping over an array by index
An array of length 5 has indexes 0, 1, 2, 3, 4. Start at 0, stop before length, step by 1. The index lets you read, change, or compare neighbouring elements (arr[i] and arr[i + 1]), which the enhanced for loop in Topic 2.7 can't do.
Write i < arr.length and never a hard-coded i < 5; when the array changes size, the loop stays correct.
int[] prices = {120, 45, 300, 80, 15};
int total = 0;
for (int i = 0; i < prices.length; i++) {
total += prices[i];
}
System.out.println("Total: " + total); // Total: 56005Counting down, stepping by more than one
The update can be any expression. i-- counts down, i += 2 takes every other value, i *= 2 doubles.
When counting down over an array, start at length - 1 and test i >= 0. Starting at length is the mirror-image off-by-one.
for (int i = 10; i >= 0; i -= 2) {
System.out.print(i + " "); // 10 8 6 4 2 0
}
System.out.println();
int[] a = {1, 2, 3};
for (int i = a.length - 1; i >= 0; i--) {
System.out.print(a[i] + " "); // 3 2 1
}06Two variables in one header
The init may declare several variables of the same type, and the update may contain several comma-separated expressions. Here i moves forward and j moves backward until they meet: that reverses an array in place by swapping the ends.
This is the two-pointer pattern. The DSA course uses it for pair sums, palindromes and partitioning.
int[] a = {1, 2, 3, 4, 5};
for (int i = 0, j = a.length - 1; i < j; i++, j--) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
// a is now {5, 4, 3, 2, 1}07Scope of the loop variable
A variable declared in the init lives only inside the loop. Using it after the closing brace is cannot find symbol. If you need the final value (say, the index where a search stopped), declare the variable before the loop.
You also can't redeclare a name that already exists in the method: int i = 10; for (int i = 0; ...) is variable i is already defined in method main(String[]). Java, unlike C++, doesn't allow a local to shadow another local.
for (int i = 0; i < 3; i++) { }
System.out.println(i); // error: cannot find symbol
int k; // declared outside: survives the loop
for (k = 0; k < 3; k++) { }
System.out.println(k); // 308Counter types that bite
A byte holds -128 to 127. In for (byte b = 0; b < 200; b++), b reaches 127, then b++ wraps it to -128 (Topic 1.3), which is still < 200. The loop never ends.
A double counter accumulates rounding error: for (double d = 0; d != 1.0; d += 0.1) skips past 1.0 (it reaches 0.9999999999999999) and runs forever. Count with an int and compute the decimal: double d = i / 10.0;.
Try it yourself
- 1
Find the last value of i
Declare
int i;before a loopfor (i = 0; i < 4; i++) { }and printiafterwards. Predict the value first. (It's 4: the value that failed the test.) - 2
Cause an off-by-one
In 'Arrays by index', change
i < prices.lengthtoi <= prices.lengthin the first loop. Run it and read the exception: the message names the bad index and the array length. - 3
Fix the double counter
Rewrite the double loop in 'Counter types that never stop' to count with
int ifrom 0 to 10 and computedouble d = i / 10.0;inside. It now stops exactly when it should.
Code & diagrams
Expected output
Up: 1 2 3 4 5
Down: 5 4 3 2 1
Evens: 0 2 4 6 8 10
Doubling: 1 2 4 8 16 32 64The second loop starts at 1 because it compares each element with the one before it.
Expected output
Total: 560
Most expensive: 300 at index 2
Price changes: -75 255 -220 -65Expected output
Before: [1, 2, 3, 4, 5, 6]
After: [6, 5, 4, 3, 2, 1]Both loops have a safety limit so the program ends; without it they would run forever.
Expected output
byte b = 0
byte b = 50
byte b = 100
byte b = -106
byte b = -56
...b wrapped around and never reached 200
double d = 0.9999999999999999, not 1.0, so d != 1.0 stays trueBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Loop one step too far
Use i <= n.length to walk an array.
public class Main {
public static void main(String[] args) {
int[] n = {1, 2, 3, 4, 5};
for (int i = 0; i <= n.length; i++) {
System.out.println(n[i]);
}
}
}Break #2
Use the counter after the loop
Print i after for (int i = 0; i < 3; i++) { }.
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) { }
System.out.println(i);
}
}Break #3
Reuse a name that's already taken
Declare int i = 10; and then write for (int i = 0; ...) in the same method.
Myth vs fact
Myth
i++ and ++i in the update make the loop run a different number of times.
Fact
The update's value is thrown away, so both just add one. The count is identical.
Myth
Caching arr.length in a variable makes the loop faster.
Fact
arr.length is a single instruction (arraylength) and the JIT hoists it. Cache it only if it reads better.
Myth
A for loop must use a counter.
Fact
All three parts are optional. for (;;) is a valid infinite loop, and the parts can hold any expressions.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Bytecode for
forand the equivalentwhileis essentially identical (if_icmpge/gotoaround aniinc). The JIT recognises counted loops (int counter, constant stride, loop-invariant limit) and applies range-check elimination, unrolling and auto-vectorisation (SIMD) to them. Alongcounter or a limit read from a non-final field can block those optimisations. - ▸
Array bounds checks are mandatory per the JVM spec, but the JIT removes them when it can prove
0 <= i < a.length, whichfor (int i = 0; i < a.length; i++)makes easy. Odd index arithmetic (a[i * 3 + k]) may keep the checks. - ▸
In the update,
i++vs++imakes no difference in Java bytecode (both becomeiinc). The C++ advice to prefer++ifor iterators doesn't apply. - ▸
Local variables in the init are scoped to the loop, so the JVM can reuse their local-variable slots afterwards;
javap -l(with-g) shows the slot ranges.
Remember this
- 1
The header has three parts separated by semicolons: init runs once before anything else; the condition is checked before every iteration; the update runs after every iteration. The order is init, test, body, update, test, body, update... until the test is false.
- 2
A variable declared in the init (
int i = 0) exists only inside the loop. After the loop,iis gone (cannot find symbol). That keeps loop counters from leaking, and lets the next loop reuse the namei. - 3
The common pattern for arrays is
for (int i = 0; i < arr.length; i++). Indexes run from0tolength - 1, so the test is<, not<=. Using<=reads one element past the end and throwsArrayIndexOutOfBoundsException. This off-by-one error is the most common loop bug in every language. - 4
Each part is optional.
for (;;)is an infinite loop. The init and update can hold several comma-separated expressions:for (int i = 0, j = n - 1; i < j; i++, j--)walks two indexes towards each other, the two-pointer technique you'll use constantly in the DSA course (/dsa). - 5
Any
forloop can be written as awhileloop and vice versa (with one difference:continuein aforstill runs the update). Chooseforwhen the loop is driven by a counter,whilewhen it's driven by a condition. - 6
Count with
int(orlongfor huge ranges), never withfloat/double(rounding means!= 1.0may never be true, Topic 1.4) and never withbyte/shortnear their limits (they wrap around and the loop never ends, Topic 1.3).
Explain it without notes
In what order do the parts of a for loop run?
Why do array loops use i < arr.length rather than i <= arr.length, and what happens if you get it wrong?
What is the difference in scope between for (int i = 0; ...) and declaring i before the loop?
Why shouldn't you use a double or a byte as a loop counter?
Practice
Print the multiplication table of 7 from 7 x 1 to 7 x 10 using a for loop.
Given int[] nums = {4, -2, 9, 0, -7, 3}, count how many are positive, negative and zero.
Compute 10! (10 factorial) with a for loop. Use a long and explain why.
Trade-offs
- ↔
The index-based for loop gives full control (any start, step, direction, neighbours, two pointers) at the cost of off-by-one risk. When you only need each element, the enhanced for loop (Topic 2.7) is safer.
- ↔
Cramming many expressions into the header (
for (int i = 0, j = n; i < j && ok; i++, j--, count++)) saves lines but hurts readability. Keep the header to start, stop and step. - ↔
Streams (
IntStream.range(0, n), Phase 11) can replace counting loops for transformations, but a plain for loop is often clearer, easier to debug and at least as fast.
Done when you can
Done when you can write a for loop over an array forwards and backwards with no off-by-one.
Done when you can state the exact execution order of init, test, body and update.
Done when you can convert a for loop to a while loop and back.
Done when you can write the two-pointer swap loop from memory.
Done when you can explain why
doubleandbytecounters are dangerous.