arrays-and-algorithms 18 min read

Walking an Array (and the Aliasing Trap)

The canonical for-loop, the bounds bug you'll write, and what happens when two names point at one array

In a nutshell

You have a row of slots. To do anything useful with it, you need to walk every slot in order. The canonical Java idiom is one shape, repeated almost everywhere:

for (int i = 0; i < arr.length; i++) {
    // do something with arr[i]
}

Three things to memorize today, in increasing subtlety:

  1. The traversal loop above is the right starting point for nearly every array task. The bug you will write first is i <= arr.length instead of i < arr.length, and it crashes loudly with ArrayIndexOutOfBoundsException.
  2. The accumulator patterns from Week 4 (sum, max, min, count) all transplant directly onto arrays. Same body shape. Same identity values for sum and count, slightly different starting values for max and min.
  3. Aliasing is what happens when two array variables point at the same array. Writes through one name show up through the other. This is your first encounter with reference semantics, and it is the central difference between arrays and the primitive variables you have used so far.

Today in three sentences. Walk with for (int i = 0; i < arr.length; i++). Off-by-one means <= where you wanted <. Two array variables can name the same array, and a write through either is visible through the other.

After this lesson, you will be able to:

  • Write a traditional for-loop traversal of an array, choosing < over <= correctly.
  • Adapt sum, max, min, and count accumulator patterns onto an array of int (and explain the seed difference for max/min vs sum/count).
  • Choose between the enhanced for-each form and the indexed for form, and predict when each is wrong for the task.
  • Trace a program in which two variables name the same array, and predict what each variable “sees” after a write.

From CSCD 110. Python’s for v in nums: is the everyday traversal because indexing in Python is an extra step (enumerate(nums) if you need the index). In Java the indexed for is the everyday default because indexing is cheap and you often need i for both reading (arr[i]) and writing (arr[i] = ...). The enhanced for is Java’s closer analogue to Python’s for v in nums: — and like Python, it gives you a copy you cannot write back through.


The canonical traversal loop

The shape:

for (int i = 0; i < arr.length; i++) {
    // body uses arr[i]
}

Read each piece.

  • int i = 0 — Start the index at zero. Indices begin at zero.
  • i < arr.length — Continue while i is strictly less than the length. Last legal index is arr.length - 1.
  • i++ — Step one slot at a time.
  • Inside the body, arr[i] reads the current slot; arr[i] = something writes to it.

Here is the smallest useful example: print every element with its index.

int[] arr = {3, 1, 4, 1, 5, 9, 2, 6};

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

Output:

arr[0] = 3
arr[1] = 1
arr[2] = 4
arr[3] = 1
arr[4] = 5
arr[5] = 9
arr[6] = 2
arr[7] = 6

When you read this loop, hold two things at once: i is the slot number (a plain int running 0, 1, 2, …), and arr[i] is the value sitting in that slot. Mixing them up is the dominant beginner array bug. If you find yourself confused about a loop, write a trace table with one column for i and another for arr[i].

Off-by-one and the bounds exception

The most common bug. Change one character, crash the program.

int[] arr = {3, 1, 4, 1, 5};   // length 5, indices 0..4

for (int i = 0; i <= arr.length; i++) {   //  <=  is the bug
    System.out.println(arr[i]);
}

That loop prints 3 1 4 1 5 and then crashes:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException:
    Index 5 out of bounds for length 5

The fix is one character: change <= to <. The reason is the math. An array of length 5 has indices 0, 1, 2, 3, 4. The condition i <= arr.length lets i reach 5, and arr[5] does not exist.

The reverse mistake (i < arr.length - 1) is also easy to write and doesn’t crash; it just silently skips the last element. That bug is harder to notice because there is no error message. Watch for it.

Common pitfall: nesting traversal loops over the same array. When you have nested loops like for (int i ...) for (int j ...), both loops can use the same condition i < arr.length and j < arr.length. Each one needs its own bound check. Sharing the bound is fine; sharing the index variable is not.

Check your understanding. Predict the output. If a line crashes, say which line.

int[] arr = {10, 20, 30};
for (int i = 0; i <= arr.length; i++) {
    System.out.println(arr[i]);
}
Reveal answer

The loop prints 10, then 20, then 30. On the next iteration i is 3, and arr[3] does not exist (legal indices are 0..2). The line System.out.println(arr[i]) throws ArrayIndexOutOfBoundsException. The fix is to change <= to <.

Check your understanding. Which of these correctly prints every element of arr?

A. for (int i = 1; i < arr.length; i++) System.out.println(arr[i]); B. for (int i = 0; i < arr.length; i++) System.out.println(arr[i]); C. for (int i = 0; i <= arr.length - 1; i++) System.out.println(arr[i]);

Reveal answer

B and C both work. C is uglier but technically correct (<= paired with length - 1 lands on the same last index as < paired with length). A skips index 0, so the first element is never printed.


Enhanced for, and when it falls short

Java has a second loop shape made for traversal: the enhanced for, also called for-each.

for (int v : arr) {
    System.out.println(v);
}

Read it as “for each int v in arr, do this.” Each iteration, v is the value in the next slot, in order from index 0 to the end. There is no i, no condition, no ++.

Use it when:

  • You only need the values, not the indices.
  • You are not writing back into the array.
  • You are reading every element exactly once, in order.

Don’t use it when:

  • You need the index i (for printing it, or for cross-referencing another array, or for binary-search-style jumps).
  • You want to write to the array (we’ll see why this fails below).
  • You need to skip elements, walk in reverse, or bail out by index.

The catch: the loop variable v is a copy of the slot’s value, not the slot itself. Assigning to v inside the loop does not change the array.

int[] arr = {1, 2, 3};
for (int v : arr) {
    v = 0;                  // changes the local copy, not the slot
}
System.out.println(arr[0]); // still prints 1

This is the same pass-by-value rule from lesson 5b, applied here: v is a fresh local variable that holds a copy of arr[i] for each slot. The slot itself is untouched. To write back, you need the indexed form:

for (int i = 0; i < arr.length; i++) {
    arr[i] = 0;
}

A practical reading test: if your loop body needs to mention arr[i] on the left side of an assignment, you need the indexed for. If it only ever appears on the right side (or as an argument or condition), the enhanced for is fine.

Common pitfall: trying to clear an array with the enhanced for. Code like for (int v : arr) v = 0; compiles, runs, and does nothing useful. The array is unchanged. Switch to the indexed form (for (int i = 0; i < arr.length; i++) arr[i] = 0;) or use Arrays.fill(arr, 0) once you meet the Arrays utility class.

Check your understanding. Which of these correctly prints Slot 0: 10, Slot 1: 20, Slot 2: 30 for int[] arr = {10, 20, 30}?

A.

for (int v : arr) {
    System.out.println("Slot " + i + ": " + v);
}

B.

for (int i = 0; i < arr.length; i++) {
    System.out.println("Slot " + i + ": " + arr[i]);
}
Reveal answer

B. The enhanced for in option A does not give you i — there is no such variable in scope. B uses the indexed form, which has both the index i and the slot value arr[i].


Accumulator patterns over arrays

Every accumulator pattern from Week 4 (sum, max, min, count) transfers directly onto arrays. Same shape, same logic, same identity values.

Sum

The identity for sum is 0: starting an accumulator at 0 and adding nothing leaves it at 0. Add each slot’s value as you walk.

int sum = 0;
for (int i = 0; i < arr.length; i++) {
    sum += arr[i];
}

Count (filtered count)

The identity for count is also 0. Increment when the predicate holds.

int negatives = 0;
for (int i = 0; i < arr.length; i++) {
    if (arr[i] < 0) {
        negatives++;
    }
}

Max and min

Max and min do not start at 0. There is no integer that is “less than every possible array value” or “greater than every possible array value” you’d want to use as a seed. Instead, seed with the first element and start the loop at index 1.

int max = arr[0];                                      // seed
for (int i = 1; i < arr.length; i++) {                 // start at 1
    if (arr[i] > max) {
        max = arr[i];
    }
}

int min = arr[0];                                      // seed
for (int i = 1; i < arr.length; i++) {                 // start at 1
    if (arr[i] < min) {
        min = arr[i];
    }
}

Two things to notice. First, the seed of arr[0] requires the array to have at least one element; otherwise the seed itself is an out-of-bounds access. (For int[] arr = {}, the line arr[0] throws.) In practice, write a precondition: if there are no elements, return some sentinel or throw an IllegalArgumentException. Second, starting the loop at i = 1 means arr[0] is not compared against itself — a small but real efficiency.

The wrong way to seed max:

int max = 0;                                           // BAD
for (int i = 0; i < arr.length; i++) {
    if (arr[i] > max) max = arr[i];
}
// for arr = {-5, -10, -3}, this returns 0, which is not in the array

If every element is negative, 0 “wins” and the answer is wrong. Always seed max/min from the first element.

Common pitfall: using Integer.MIN_VALUE and Integer.MAX_VALUE as seeds. It works, but it relies on the reader knowing those constants and trusting that no array element will exceed them. The “seed from arr[0], start at i = 1” idiom is the Reges-style convention used in this course; use it.

Check your understanding. Given int[] arr = {3, -1, 7, 4, 7}, trace these and give each accumulator’s final value.

A. Sum. B. Count of values that equal 7. C. Max.

Reveal answer

A. 3 + (-1) + 7 + 4 + 7 = 20. B. The slots arr[2] and arr[4] both equal 7, so the count is 2. C. Seed max = arr[0] = 3. Compare with -1 (no), 7 (yes, max becomes 7), 4 (no), 7 (no, not strictly greater). Final max: 7.

Check your understanding. What is the bug in this max code? What does it return for int[] arr = {-2, -7, -5}?

int max = 0;
for (int i = 0; i < arr.length; i++) {
    if (arr[i] > max) max = arr[i];
}
Reveal answer

The bug is the seed: max = 0 is fine when at least one element is non-negative, but it returns 0 (which is not in the array) when every element is negative. For {-2, -7, -5}, the loop never enters the if branch, and max stays at 0. The correct seed is arr[0], with the loop starting at i = 1.


Aliasing: two names, one array

Now the part that distinguishes arrays from every primitive variable you have used so far.

Recall from lesson 5b: when you assign one variable to another, Java copies the value on the right side into the variable on the left. For int x = 5; int y = x; y = 99;, x stays at 5 because primitives are copied by value.

Arrays do not copy that way. The “value” of an array variable is a reference to the array object. So this code does not make a copy:

int[] a = {1, 2, 3};
int[] b = a;            // b now references the SAME array as a

After that line, a and b are two names for the same single array object on the heap. Picture it:

a ----\
       \---> [ 1 | 2 | 3 ]
       /
b ----/

A write through either name lands in the one shared array. Both names see the change.

int[] a = {1, 2, 3};
int[] b = a;

b[0] = 99;
System.out.println(a[0]);    // prints 99
System.out.println(b[0]);    // prints 99

This is aliasing: two variables aliased to the same object. It is not a Java oddity — it is how every reference-typed variable in Java works (and how every Python list works). It is the first time it bites you because primitives never had this problem.

The most common confusion: students assume that int[] b = a; makes a copy of the array. It does not. To make a true copy, you have to allocate a new array and copy the elements over:

int[] copy = new int[a.length];
for (int i = 0; i < a.length; i++) {
    copy[i] = a[i];
}
// now copy and a are separate arrays

(There is also a one-liner int[] copy = a.clone(); and the standard library helper Arrays.copyOf(a, a.length). You will see those after the lab.)

Tracing aliasing

Here is a worked aliasing trace from the in-class slides.

int[] a = {4, 1, 3, 2, 5};
int[] b = a;
int x = 0;
for (int i = 0; i < a.length; i++) {
    if (a[i] > x) {
        x = a[i];
        b[i] = 0;
    }
}
i a[i] x before a[i] > x? x after action
0 4 0 yes 4 b[0] = 0
1 1 4 no 4
2 3 4 no 4
3 2 4 no 4
4 5 4 yes 5 b[4] = 0

After the loop, x is 5, and the array (which both a and b name) is {0, 1, 3, 2, 0}. Reading a shows {0, 1, 3, 2, 0}, and so does reading b. Writes through b were visible through a because they were always the same array.

Common pitfall: assuming int[] copy = a; makes a defensive copy. It does not. It makes a second name for the same array. If your code goes on to mutate copy thinking the original a is safe, you will mutate both. To copy an array, allocate a new one and copy each slot, or use a.clone() / Arrays.copyOf.

Check your understanding. What does this print?

int[] a = {1, 2, 3};
int[] b = a;
b[1] = 99;
a[2] = 77;
System.out.println(a[0] + " " + a[1] + " " + a[2]);
System.out.println(b[0] + " " + b[1] + " " + b[2]);
Reveal answer

Both lines print 1 99 77. a and b are two names for the same array. The write b[1] = 99 and the write a[2] = 77 both land in the shared array. Reading through either name shows the same data.

Check your understanding. Now suppose you replace int[] b = a; with int[] b = a.clone();. What does the same program print?

Reveal answer

Now b is a separate array (made fresh by clone), so writes through b do not affect a and vice versa. After b[1] = 99 and a[2] = 77:

1 2 77       (a)
1 99 3       (b)

Each name sees only its own writes.


Wrap up and what’s next

Recap.

  • for (int i = 0; i < arr.length; i++) is the everyday traversal. Use < (not <=).
  • Enhanced for for (int v : arr) is read-only; you cannot write back through v. Use it when you need the values but not the index.
  • Sum and count seed at 0. Max and min seed at arr[0] and start the loop at i = 1.
  • Two array variables can name the same array. Writes through either name are visible through the other. To make a real copy, allocate a new array and copy the slots (or call clone() / Arrays.copyOf).

What you can do now. Walk any array with the indexed for. Spot off-by-one bugs. Pick the right seed for any accumulator. Trace a program where two variables alias the same array.

Next up: Arrays as Method Parameters. Aliasing has direct consequences for methods: when you pass an array to a method, you pass the reference. The method and the caller name the same array, so a method can mutate the caller’s data through that shared name. Then you’ll learn the pattern for “growing” or “shrinking” an array — return a new one — and the two-pass count-then-fill idiom.


  • Reges & Stepp, Building Java Programs, Chapter 7 sections 7.1 (length), 7.2 (traversal), 7.3 (algorithms over arrays).
  • FAQ entries on ArrayIndexOutOfBoundsException and on cloning vs aliasing.