Command Palette

Search for a command to run...

PHASE 2Beginner ~30 min· topic 6 of 10

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 for header. It runs once, before the loop starts, usually to declare a counter.
Update
The third part of a for header. It runs after every iteration, usually i++.
Counter
A variable that counts iterations, conventionally named i, j or k.
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.

Main.javawhole filejava
for (int i = 1; i <= 5; i++) {
    System.out.println("Step " + i);
}
terminal
$ java Main.java
── expected output ──
Step 1
Step 2
Step 3
Step 4
Step 5

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.

The exact order of executiondiagram
Rendering diagram…

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.

Main.javawhole filejava
// 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.

Main.javawhole filejava
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: 560

05Counting 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.

Main.javawhole filejava
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.

Main.javawhole filejava
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.

Main.javawhole filejava
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);        // 3

08Counter 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. 1

    Find the last value of i

    Declare int i; before a loop for (i = 0; i < 4; i++) { } and print i afterwards. Predict the value first. (It's 4: the value that failed the test.)

  2. 2

    Cause an off-by-one

    In 'Arrays by index', change i < prices.length to i <= prices.length in the first loop. Run it and read the exception: the message names the bad index and the array length.

  3. 3

    Fix the double counter

    Rewrite the double loop in 'Counter types that never stop' to count with int i from 0 to 10 and compute double d = i / 10.0; inside. It now stops exactly when it should.

Code & diagrams

Counting up, down and in steps New tab
Sign in to run this example in your browser.

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 64
Arrays by index: total, max and position New tab

The second loop starts at 1 because it compares each element with the one before it.

Sign in to run this example in your browser.

Expected output

Total: 560
Most expensive: 300 at index 2
Price changes: -75 255 -220 -65
Two pointers: reverse an array in place New tab
Sign in to run this example in your browser.

Expected output

Before: [1, 2, 3, 4, 5, 6]
After:  [6, 5, 4, 3, 2, 1]
Counter types that never stop New tab

Both loops have a safety limit so the program ends; without it they would run forever.

Sign in to run this example in your browser.

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 true

Break 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.

Main.javawhole filejava
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]);
        }
    }
}
terminal
$ javac Main.java && java Main
── what you'll see ──
1
2
3
4
5
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
at Main.main(Main.java:5)

Break #2

Use the counter after the loop

Print i after for (int i = 0; i < 3; i++) { }.

Main.javawhole filejava
public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) { }
        System.out.println(i);
    }
}
terminal
$ javac Main.java
── what you'll see ──
Main.java:4: error: cannot find symbol
System.out.println(i);
^
symbol: variable i
location: class Main
1 error

Break #3

Reuse a name that's already taken

Declare int i = 10; and then write for (int i = 0; ...) in the same method.

terminal
$ javac Main.java
── what you'll see ──
Main.java:4: error: variable i is already defined in method main(String[])
for (int i = 0; i < 3; i++) { }
^
1 error

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 for and the equivalent while is essentially identical (if_icmpge/goto around an iinc). The JIT recognises counted loops (int counter, constant stride, loop-invariant limit) and applies range-check elimination, unrolling and auto-vectorisation (SIMD) to them. A long counter 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, which for (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 ++i makes no difference in Java bytecode (both become iinc). The C++ advice to prefer ++i for 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. 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. 2

    A variable declared in the init (int i = 0) exists only inside the loop. After the loop, i is gone (cannot find symbol). That keeps loop counters from leaking, and lets the next loop reuse the name i.

  3. 3

    The common pattern for arrays is for (int i = 0; i < arr.length; i++). Indexes run from 0 to length - 1, so the test is <, not <=. Using <= reads one element past the end and throws ArrayIndexOutOfBoundsException. This off-by-one error is the most common loop bug in every language.

  4. 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. 5

    Any for loop can be written as a while loop and vice versa (with one difference: continue in a for still runs the update). Choose for when the loop is driven by a counter, while when it's driven by a condition.

  6. 6

    Count with int (or long for huge ranges), never with float/double (rounding means != 1.0 may never be true, Topic 1.4) and never with byte/short near their limits (they wrap around and the loop never ends, Topic 1.3).

Explain it without notes

01

In what order do the parts of a for loop run?

02

Why do array loops use i < arr.length rather than i <= arr.length, and what happens if you get it wrong?

03

What is the difference in scope between for (int i = 0; ...) and declaring i before the loop?

04

Why shouldn't you use a double or a byte as a loop counter?

Practice

01

Print the multiplication table of 7 from 7 x 1 to 7 x 10 using a for loop.

02

Given int[] nums = {4, -2, 9, 0, -7, 3}, count how many are positive, negative and zero.

03

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 double and byte counters are dangerous.