CSCD210

Traditional for loop traversal

Skillcscd210-traditional-for-loop-traversalTextbookBJP Ch 7

The classic for (int i = 0; i < a.length; i++) loop

Student can produce a traditional for loop header that visits every element of an array in order, using int i = 0, i < a.length, and i++, and explain which part of the loop body needs the index variable (versus only the value).

The traditional counted for loop is the canonical way to walk every slot of an array in order. Its shape is fixed enough that an experienced reader recognizes it at a glance, and the recognition is the point.

for (int i = 0; i < xs.length; i++) {
    System.out.println(xs[i]);
}

The three parts of the header each play a specific role (JLS §14.14.1):

1. Initialization (int i = 0) declares the loop counter and sets it to the first valid index. The declaration is inside the header, so i exists only for the duration of the loop. 2. Condition (i < xs.length) runs before each iteration. The loop body runs while this expression is true. The strict <, which the loop-bound rule states, is what keeps i inside the valid index range. 3. Update (i++) runs after each iteration. The unary ++ increments the counter by one, which advances to the next slot.

When you need the index during the body (to look at the previous element, to access a parallel array, to print the slot number), this loop is the right tool. The enhanced-for loop, written for (int v : xs), gives you the value but hides the index.

Reading the loop as a contract

The loop body sees i taking every integer value from 0 to xs.length - 1, in that order, one value per iteration. Inside the body, xs[i] is the element at the current position, xs[i-1] is the previous (when defined), and xs[i+1] is the next (when defined). The loop guarantees the order; the body acts on each slot once.

When you want to start somewhere else

The header is parameterizable: int i = 1 skips the first element; i += 2 visits every other slot; i = xs.length - 1 paired with i >= 0 and i-- walks the array in reverse. The mental model is "I am the index: where am I starting, what stops me, how do I move?"

In other languages

  • C: identical shape: for (int i = 0; i < n; i++) { / ... / }. Java inherits the C for syntax verbatim.
  • Python: for i in range(len(xs)): is the analog; the more idiomatic form is for v in xs: (Java's enhanced-for, area 05). Python lacks the C-style three-part header.
  • JavaScript: identical to Java; for (let i = 0; i < arr.length; i++). Modern JS often uses for (const v of arr) instead.

i < a.length (strict less-than) is the right bound

Student can identify i < a.length as the correct loop bound for a forward array traversal, explain the half-open-interval convention that motivates the strict <, and predict the runtime outcome of using <= instead.

The loop condition that visits every slot of xs and stops at exactly the right time is i < xs.length. Strict less-than. Not <=. The reason is the half-open interval convention that pervades Java's array model:

  • Valid indices: 0, 1, …, xs.length - 1
  • Equivalent interval notation: [0, xs.length) ← the right endpoint is excluded

Reading the condition i < xs.length as "while i is a valid index" makes the loop self-evident. Every iteration starts with i in range, runs the body once on xs[i], then increments, and the loop stops the first time i reaches xs.length, which is one past the last valid index.

final int[] xs = {10, 20, 30};        //  length 3, valid indices 0..2
for (int i = 0; i < xs.length; i++) { //  runs with i = 0, 1, 2
    System.out.println(xs[i]);        //  prints 10, 20, 30: done
}

The accidental swap to <= produces one extra iteration with i == xs.length, and xs[xs.length] is out of range: ArrayIndexOutOfBoundsException, which array access and length covers. Crucially, the exception fires at the end of the traversal, after every prior iteration succeeded. Debugging is harder because the visible symptom is "the loop crashed at the end," not "the loop has the wrong bound."

Why half-open

The half-open [0, length) convention has practical consequences worth noticing:

  • The number of iterations is length - 0 == length: easy mental math.
  • The loop is symmetric for any subrange: for (int i = a; i < b; i++) visits b - a elements.
  • Two adjacent ranges, [a, m) and [m, b), partition [a, b) without overlap or gap. (Used by binary search and the merge step of merge sort.)

Closed-interval conventions ([0, length]) lack all three properties. The half-open form is so universal in modern languages that even Python's range(n) produces 0, 1, …, n-1 for the same reason.

A safety net for null and empty

If xs is null, the access xs.length throws NullPointerException before the loop ever runs: the bug is at the array reference, not at the loop bound. If xs.length == 0, the condition 0 < 0 is immediately false; the loop body runs zero times and the program continues. Both behaviors are intentional and useful: the empty-array case is the reason return new int[0] is preferable to return null, which the rule for returning an array from a method states.

In other languages

  • C: identical: for (int i = 0; i < n; i++). C does not bounds-check, so the consequence of <= is a buffer overrun, not an exception.
  • Python: range(len(xs)) produces the half-open [0, len) automatically. The equivalent off-by-one error needs range(len(xs) + 1), which is harder to write accidentally.
  • Rust: for i in 0..xs.len() is the half-open form; 0..=xs.len() is the closed-interval form (almost always wrong for indexing). The syntax makes the choice explicit.