The Stacks
UNIT 4: DATA COLLECTIONS · TOPIC 4.9

4.9 ArrayList Traversals

Same two loop styles as arrays, with one extra hazard: modifying the list's size while looping over it.

What you need to know

  • Standard: for (int i = 0; i < list.size(); i++) with list.get(i). Enhanced: for (String s : list).
  • Removing during a forward loop skips elements. After remove(i), the next element shifts into index i, then i++ jumps past it.
  • Fixes: after removing, do i-- (or don't increment); or traverse backward with for (int i = list.size() - 1; i >= 0; i--), where removals don't affect unvisited indices.
  • Never add or remove inside an enhanced for loop — it throws ConcurrentModificationException.
  • Enhanced for over an ArrayList of objects lets you call mutators on each element (the reference is shared); over ArrayList<Integer>, assigning to the loop variable does nothing to the list.
  • The loop condition i < list.size() is re-evaluated each iteration, so a shrinking list shortens the loop.

Worked example

ArrayList<Integer> a = new ArrayList<Integer>();
// a = [4, 4, 7, 4]
// BUGGY: remove all 4s
for (int i = 0; i < a.size(); i++)
{
    if (a.get(i) == 4) a.remove(i);
}
// result: [4, 7]  — second 4 was skipped

// CORRECT: backward
for (int i = a.size() - 1; i >= 0; i--)
{
    if (a.get(i) == 4) a.remove(i);
}
// result: [7]

Trace it yourself

Step through with the buttons, or use the ← → keys. Changed variables are highlighted.

Exam tip: If a question removes elements in a forward loop, trace it literally — the answer usually has "leftover" elements that were skipped. If asked for the correct version, pick the backward loop or the one with i-- after remove.

Going deeper

The nuance, edge cases, and connections that turn a 3 into a 5.

  • The removal bug, step by step: list [2, 2, 5], remove all 2s with a forward loop. i=0: get(0)=2, remove → [2, 5]. i=1: get(1)=5, no. Done. Result [2, 5] — the second 2 shifted into index 0 and was never examined.
  • Fix A — backward: for (int i = list.size() - 1; i >= 0; i--). Removing at i shifts elements after i, which you've already visited. Unvisited elements (before i) keep their indices.
  • Fix B — don't advance after removing: use a while loop with manual increment, or i-- right after remove(i) in a for loop. Either way, the shifted-in element is re-examined.
  • Enhanced for + structural change = ConcurrentModificationException. Adding or removing inside for (T x : list) throws at run time (not compile time). Calling mutators on the elements is fine; changing the list's size is not.
  • Adding in a forward loop: inserting at i pushes the current element to i+1, so i++ lands on it again — potential infinite loop if you insert every time. Insert, then i++ extra to skip.
  • size() is re-evaluated each iteration. A shrinking list ends the loop sooner; a growing list extends it. This is different from a for loop over an array, where length is fixed.
  • Object elements: for (Student s : roster) s.setGrade(…) changes the objects in the list — the reference is shared. for (Integer n : nums) n = 0; changes nothing — n is a copy.

Mistakes that cost points

  • Forward-loop removal. Skips the element after each removal. The exam asks for the buggy output; trace it literally.
  • Removing inside an enhanced for. Run-time exception.
  • Caching size() before the loop. If the list shrinks, you go out of bounds.

Practice questions

Written in the style of the real exam. Try each one before revealing the answer.

Q1 What is the contents of list after the following code, where list starts as [1, 2, 2, 3, 2]?
for (int i = 0; i < list.size(); i++)
{
    if (list.get(i) == 2)
    {
        list.remove(i);
    }
}
  1. A [1, 3]
  2. B [1, 2, 3]
  3. C [1, 2, 3, 2]
  4. D [1, 3, 2]
Show answer

Answer: B. i=1 removes the first 2 → [1, 2, 3, 2]; i=2 is 3 (the second 2 shifted to index 1 and was skipped); i=3 removes the last 2 → [1, 2, 3].

Q2 Which of the following correctly removes every element equal to target from an ArrayList<Integer> nums?
  1. A for (Integer n : nums) { if (n == target) nums.remove(n); }
  2. B for (int i = nums.size() - 1; i >= 0; i--) { if (nums.get(i) == target) nums.remove(i); }
  3. C for (int i = 0; i < nums.size(); i++) { if (nums.get(i) == target) nums.remove(i); }
  4. D for (int i = 0; i <= nums.size(); i++) { if (nums.get(i) == target) nums.remove(i); }
Show answer

Answer: B. Backward traversal handles removals safely. A throws ConcurrentModificationException; C skips adjacent matches; D goes out of bounds.

Key vocabulary

ConcurrentModificationException
the error from modifying an ArrayList inside an enhanced for loop
Backward traversal
looping from size - 1 down to 0, safe for removals