Historical Context & Motivation
Fixed-size arrays have been a cornerstone of programming since the earliest high-level languages, but their rigid length requirement poses a fundamental design problem: developers must predict collection sizes at compile time. When Java was released in 1995, its creators recognized that real-world applications—inventory systems, student rosters, game entities—frequently need collections that grow and shrink at runtime. The Java Collections Framework (JCF), introduced in Java 2, addressed this gap by providing a unified architecture of interfaces and implementations for dynamic data structures. At the center of the framework sits ArrayList, a resizable-array implementation of the List interface that combines the random-access speed of an array with the flexibility of automatic resizing.
The core question ArrayList answers is deceptively simple: how do you maintain indexed, ordered access to elements while allowing the collection to change size on the fly? Understanding the methods that manipulate an ArrayList—adding, removing, accessing, and modifying elements—is essential for the AP exam and for writing robust Java programs in any professional context.
Core Principles & Definitions
An ArrayList is a generic class in the java.util package that implements the List interface. It stores references to objects (not primitives) in a contiguous internal array and automatically resizes that backing array when capacity is exhausted. Because ArrayList uses zero-based indexing identical to standard arrays, transitioning between the two data structures is conceptually smooth. However, unlike arrays, ArrayList tracks its own size (the number of elements currently stored) independently of its internal capacity.
Dynamic Sizing
Object References Only
int must be wrapped as Integer via autoboxing.Zero-Based Indexing
size() − 1. Out-of-bounds access throws IndexOutOfBoundsException.Index Shifting
Generics & Type Safety
ArrayList<String> restricts the list to String objects, and the compiler enforces this constraint, preventing ClassCastException at runtime.Visual Explanation — ArrayList in Memory
add(2, "X"), elements at indices 2–3 shift right. After remove(1), elements at indices 2–4 shift left and size decreases by one.The diagram above captures the two most exam-critical operations: indexed insertion and indexed removal. When you call add(2, "X"), every element from index 2 onward shifts one position to the right before the new element is placed at index 2. When you call remove(1), the element at index 1 is returned and every element from index 2 onward shifts one position to the left, closing the gap. Both operations mutate the size() accordingly, and the internal capacity remains unchanged unless a resize is triggered.
How ArrayList Methods Work
AP-Tested ArrayList Methods
| Method Signature | Return Type | Behavior |
|---|---|---|
add(E obj) | boolean | Appends obj to the end; always returns true. |
add(int index, E obj) | void | Inserts obj at index; shifts subsequent elements right. |
get(int index) | E | Returns the element at index without modifying the list. |
set(int index, E obj) | E | Replaces the element at index with obj; returns the old element. |
remove(int index) | E | Removes and returns the element at index; shifts subsequent elements left. |
size() | int | Returns the number of elements currently in the list (not the capacity). |
Notice that add(E obj) returns boolean while add(int index, E obj) returns void. Similarly, set and remove both return the displaced element, which is useful when you need to capture or inspect the value being replaced or deleted. The get method is a pure accessor—it never mutates the list. On the AP exam, questions frequently test whether students know which methods modify the list and which do not.
Traversal & Modification Patterns
Traversing an ArrayList while simultaneously modifying it is one of the trickiest topics on the AP CSA exam. There are two primary traversal mechanisms: the standard indexed for loop and the enhanced for loop (for-each). The indexed loop grants full control over the index variable, making it safe to add or remove elements when you manage the index correctly. The enhanced for loop, by contrast, does not expose an index and will throw a ConcurrentModificationException if you modify the list during iteration.
i on a removal pass; the left-shift brings the next element to the current index. Alternatively, traverse backwards from size() − 1 to 0, which avoids this issue entirely.Worked Example — Building and Querying a Roster
Consider the following scenario: a teacher maintains a student roster as an ArrayList<String>. The code below builds the roster, performs several mutations, and prints the final state. Trace each operation carefully.
ArrayList<String> roster = new ArrayList<String>();
roster.add("Alice"); roster.add("Bob"); roster.add("Carol"); roster.add("Dave");roster.add(1, "Eve");
Inserts "Eve" at index 1. "Bob", "Carol", and "Dave" each shift one position to the right.String old = roster.set(3, "Carlos");
Replaces the element at index 3 ("Carol") with "Carlos". The method returns the old value "Carol", stored in old.roster.remove(0);
Removes and returns "Alice". All remaining elements shift left.System.out.println(roster.get(2) + " " + roster.size());
roster.get(2) returns "Carlos" (index 2 after the previous removal). roster.size() returns 4.ArrayList vs. Array — Strengths & Limitations
| Feature | Array | ArrayList |
|---|---|---|
| Size | Fixed at creation (.length) | Dynamic (.size()) |
| Primitives | Supports int, double, etc. directly | Requires wrapper classes (Integer, Double) |
| Access syntax | arr[i] | list.get(i) |
| Mutation syntax | arr[i] = val | list.set(i, val) |
| Insert / remove | Manual shifting required | Built-in methods with automatic shifting |
| Performance (random access) | O(1) | O(1) |
| Performance (insert/remove middle) | O(n) — manual | O(n) — automatic |
Connection to Advanced Data Structures
| ArrayList (AP CSA) | Advanced Alternatives |
|---|---|
| Backed by a resizable array; O(n) insert/remove in the middle. | LinkedList — O(1) insert/remove at known position, but O(n) random access. |
| Not thread-safe; for single-threaded use. | CopyOnWriteArrayList — thread-safe variant that clones the backing array on each write. |
| Stores only objects; autoboxing adds overhead. | IntStream / primitive arrays — avoid boxing entirely for numeric workloads. |
| Ordered by insertion; no duplicate control. | HashSet / TreeSet — enforce uniqueness; TreeSet also maintains sorted order. |
In a college-level data structures course, you will formalize the performance characteristics of ArrayList in terms of amortized analysis. Although a single add that triggers a resize copies all n elements (O(n)), the doubling strategy ensures that the average cost per insertion is O(1) amortized. Understanding this tradeoff is foundational for algorithm design, and the intuition you build tracing ArrayList operations on the AP exam transfers directly to these more advanced analyses.
Practice Problems
list.add("X")
B) list.set(0, "Y")
C) list.get(2)
D) list.remove(1)ArrayList<String> items = new ArrayList<String>();
items.add("P");
items.add("Q");
items.add("R");
items.add(1, "S");
items.set(3, "T");
items.remove(0);
What does items contain after execution?
A) ["S", "Q", "T"]
B) ["Q", "S", "T"]
C) ["S", "Q", "R"]
D) ["P", "S", "T"]ArrayList<Integer> nums = new ArrayList<Integer>();
nums.add(5);
nums.add(10);
nums.add(15);
nums.add(20);
for (int i = nums.size() - 1; i >= 0; i--) {
if (nums.get(i) % 10 == 0) {
nums.remove(i);
}
}
System.out.println(nums);
A) [5, 15]
B) [5, 10, 15]
C) [5, 15, 20]
D) [5]removeDuplicates that takes an ArrayList<String> and removes all duplicate values so that only the first occurrence of each string remains. The order of remaining elements must be preserved. Do not use any data structures other than ArrayList.
Method signature:
public static void removeDuplicates(ArrayList<String> list)ArrayList<Integer>:
for (int i = 0; i < nums.size(); i++) {
if (nums.get(i) < 0) {
nums.remove(i);
}
}
(a) Explain the specific bug in this code by giving an input that produces incorrect output.
(b) Provide two distinct fixes and explain why each works.