ArrayList
IntermediateThe most-used Java collection — dynamic arrays with O(1) random access, amortised O(1) add, and rich utility methods.
Overview
ArrayList is a resizable array implementation of the List interface. Internally it stores elements in a plain Object[] array. When the array is full, ArrayList allocates a new array 50% larger and copies all elements — this is the resize (grow) operation that makes add amortised O(1). Random access (get, set) is O(1). Insertion or deletion in the middle is O(n) because elements must be shifted. For the vast majority of use cases ArrayList outperforms LinkedList due to CPU cache locality.
Core Operations & Performance
Key ArrayList operations and their time complexity:
add(E) — amortised O(1); O(n) on resize add(index, E) — O(n) — shifts elements right get(index) — O(1) — direct array access set(index, E) — O(1) remove(index) — O(n) — shifts elements left remove(Object) — O(n) — linear search then shift contains(Object) — O(n) — linear search size() — O(1)
Pre-size with new ArrayList<>(expectedSize) to avoid repeated resizes when you know the approximate count upfront.
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
import java.util.Iterator;
public class ArrayListDemo {
public static void main(String[] args) {
// Constructors
List<String> list = new ArrayList<>(); // default capacity 10
List<String> sized = new ArrayList<>(100); // pre-sized
List<String> copy = new ArrayList<>(List.of("a", "b")); // copy constructor
// Adding
list.add("apple");
list.add("banana");
list.add("cherry");
list.add(1, "avocado"); // insert at index 1 — O(n)
System.out.println(list); // [apple, avocado, banana, cherry]
// Getting & setting — O(1)
System.out.println(list.get(2)); // banana
list.set(2, "blueberry");
System.out.println(list.get(2)); // blueberry
// Removing
list.remove(0); // by index — O(n)
list.remove("cherry"); // by value — O(n)
System.out.println(list); // [avocado, blueberry]
// Bulk operations
List<String> more = List.of("date", "elderberry");
list.addAll(more);
list.addAll(0, List.of("fig")); // insert all at index 0
System.out.println(list); // [fig, avocado, blueberry, date, elderberry]
// Searching
System.out.println(list.contains("date")); // true
System.out.println(list.indexOf("date")); // 3
System.out.println(list.size()); // 5
System.out.println(list.isEmpty()); // false
// Sub-list view (backed by original!)
List<String> sub = list.subList(1, 4);
System.out.println(sub); // [avocado, blueberry, date]
sub.clear(); // modifies the original list too
System.out.println(list); // [fig, elderberry]
}
}Sorting, Iterating & removeIf
ArrayList.sort() (Java 8+) accepts a Comparator and uses TimSort — O(n log n), stable. Collections.sort() does the same. Sorting in reverse: pass Comparator.reverseOrder() or Collections.reverseOrder().
Iterating: enhanced for-each (cleanest), iterator (safe removal during iteration), ListIterator (bidirectional + set during iteration), forEach with lambda (Java 8+).
Never remove elements from an ArrayList using a regular for-index loop while iterating — it shifts elements and you skip items. Use Iterator.remove(), removeIf(), or iterate backwards.
import java.util.*;
import java.util.stream.Collectors;
public class ArrayListIterating {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>(
List.of("banana", "apple", "cherry", "avocado", "date"));
// Sort ascending (natural order)
Collections.sort(fruits);
System.out.println(fruits); // [apple, avocado, banana, cherry, date]
// Sort by length, then alphabetically
fruits.sort(Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
System.out.println(fruits); // [date, apple, banana, cherry, avocado]
// Sort descending
fruits.sort(Comparator.reverseOrder());
System.out.println(fruits); // [date, cherry, banana, avocado, apple]
// Safe removal during iteration with Iterator
Iterator<String> it = fruits.iterator();
while (it.hasNext()) {
if (it.next().startsWith("a")) it.remove(); // safe
}
System.out.println(fruits); // [date, cherry, banana]
// removeIf — cleanest bulk removal (Java 8+)
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
nums.removeIf(n -> n % 2 == 0);
System.out.println(nums); // [1, 3, 5]
// replaceAll — transform each element in-place (Java 8+)
List<String> words = new ArrayList<>(List.of("hello", "world"));
words.replaceAll(String::toUpperCase);
System.out.println(words); // [HELLO, WORLD]
// Convert to array
String[] arr = words.toArray(new String[0]);
System.out.println(Arrays.toString(arr)); // [HELLO, WORLD]
}
}ArrayList Internals & Capacity Management
Internally ArrayList wraps an Object[] called elementData. Default initial capacity is 10. When the array is full, grow() creates a new array with capacity = oldCapacity + (oldCapacity >> 1) (roughly 1.5×) and copies elements via System.arraycopy.
ensureCapacity(n) pre-allocates room for n elements in one shot, avoiding repeated resizes during bulk inserts. trimToSize() shrinks the backing array to the exact current size, useful when the list is built up then only read.
The modCount field counts structural modifications. Iterators snapshot it; any structural change during iteration raises ConcurrentModificationException (fail-fast).
import java.util.ArrayList;
public class ArrayListInternals {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>(); // capacity = 10
// Pre-allocate to avoid resizes when we know the size
list.ensureCapacity(1_000_000);
// Bulk insert — no resize overhead because we pre-allocated
for (int i = 0; i < 1_000_000; i++) list.add(i);
System.out.println(list.size()); // 1000000
// Shrink backing array to exact size (saves memory)
list.trimToSize();
// Fail-fast: ConcurrentModificationException
ArrayList<String> names = new ArrayList<>(java.util.List.of("a", "b", "c"));
try {
for (String name : names) {
if (name.equals("a")) names.remove(name); // modifies during iteration
}
} catch (java.util.ConcurrentModificationException e) {
System.out.println("ConcurrentModificationException caught");
}
// Safe: removeIf avoids CME
names.removeIf(name -> name.equals("b"));
System.out.println(names); // [c]
// toArray with correct type
Object[] objArr = names.toArray(); // Object[]
String[] typedArr = names.toArray(String[]::new); // String[] (Java 11+)
}
}Interactive Visualization
Key Points to Remember
- ArrayList is backed by Object[] — O(1) get/set, amortised O(1) add at end, O(n) insert/remove in middle
- Default capacity is 10; growth factor is ~1.5× — use ensureCapacity() for large batch inserts
- Never remove by index in a forward loop — use Iterator.remove() or removeIf() instead
- subList() returns a view backed by the original — mutations to the view affect the original
- ArrayList is fail-fast: structural modification during for-each throws ConcurrentModificationException
- For thread safety use CopyOnWriteArrayList (read-heavy) or Collections.synchronizedList() (write-heavy)
Practice ArrayList in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat is the internal data structure of ArrayList?
What is the growth strategy of ArrayList when it runs out of space?
Why does removing elements in a for-each loop throw ConcurrentModificationException?
What is the time complexity of ArrayList.contains()?
What is the difference between ArrayList and Vector?
Ask Aria about ArrayList
Your personal AI tutor — ask anything about this concept