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:
- The traversal loop above is the right starting point for nearly every array task. The bug you will write first is
i <= arr.lengthinstead ofi < arr.length, and it crashes loudly withArrayIndexOutOfBoundsException. - 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.
- 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-eachform and the indexedforform, 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 needifor both reading (arr[i]) and writing (arr[i] = ...). The enhanced for is Java’s closer analogue to Python’sfor 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 whileiis strictly less than the length. Last legal index isarr.length - 1.i++— Step one slot at a time.- Inside the body,
arr[i]reads the current slot;arr[i] = somethingwrites 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 conditioni < arr.lengthandj < 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, then20, then30. On the next iterationiis3, andarr[3]does not exist (legal indices are0..2). The lineSystem.out.println(arr[i])throwsArrayIndexOutOfBoundsException. 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 withlength - 1lands on the same last index as<paired withlength). A skips index0, 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 useArrays.fill(arr, 0)once you meet theArraysutility class.
Check your understanding. Which of these correctly prints
Slot 0: 10,Slot 1: 20,Slot 2: 30forint[] 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 indexiand the slot valuearr[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_VALUEandInteger.MAX_VALUEas seeds. It works, but it relies on the reader knowing those constants and trusting that no array element will exceed them. The “seed fromarr[0], start ati = 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 slotsarr[2]andarr[4]both equal7, so the count is2. C. Seedmax = arr[0] = 3. Compare with-1(no),7(yes, max becomes7),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 = 0is fine when at least one element is non-negative, but it returns0(which is not in the array) when every element is negative. For{-2, -7, -5}, the loop never enters theifbranch, andmaxstays at0. The correct seed isarr[0], with the loop starting ati = 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 mutatecopythinking the originalais safe, you will mutate both. To copy an array, allocate a new one and copy each slot, or usea.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.aandbare two names for the same array. The writeb[1] = 99and the writea[2] = 77both land in the shared array. Reading through either name shows the same data.
Check your understanding. Now suppose you replace
int[] b = a;withint[] b = a.clone();. What does the same program print?Reveal answer
Now
bis a separate array (made fresh byclone), so writes throughbdo not affectaand vice versa. Afterb[1] = 99anda[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 throughv. Use it when you need the values but not the index. - Sum and count seed at
0. Max and min seed atarr[0]and start the loop ati = 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.
Related resources
- Reges & Stepp, Building Java Programs, Chapter 7 sections 7.1 (
length), 7.2 (traversal), 7.3 (algorithms over arrays). - FAQ entries on
ArrayIndexOutOfBoundsExceptionand on cloning vs aliasing.