JavaArrayList

ArrayList

ArrayList is the most commonly used implementation of the List interface. Internally, it is backed by a plain resizable array — when it fills up, Java allocates a larger array behind the scenes and copies the existing elements over, which is invisible to your code but explains its performance characteristics.
Creating and Populating an ArrayList

Basic ArrayList usage

Java
import java.util.List;
import java.util.ArrayList;

public class ArrayListDemo {
    public static void main(String[] args) {
        List<String> tasks = new ArrayList<>();
        tasks.add("Write report");
        tasks.add("Review code");
        tasks.add("Deploy release");

        System.out.println(tasks);
        System.out.println("First task: " + tasks.get(0));
        System.out.println("Total tasks: " + tasks.size());
    }
}
[Write report, Review code, Deploy release]
First task: Write report
Total tasks: 3
Performance Characteristics
Because an ArrayList is backed by an array, accessing any element by index is O(1) — a direct memory offset calculation, regardless of the list's size.

O(1) index access, regardless of size

Java
import java.util.List;
import java.util.ArrayList;

public class FastAccessDemo {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        for (int i = 0; i < 1_000_000; i++) {
            numbers.add(i);
        }
        System.out.println(numbers.get(999_999)); // just as fast as get(0)
    }
}
Warning
Inserting or removing an element in the middle of an ArrayList is O(n), not O(1). Every element after the insertion or removal point has to shift by one position in the underlying array to keep it contiguous. Removing the very first element of a large list is the worst case — it shifts every remaining element down by one.

Insertion in the middle shifts every element after it

Java
import java.util.List;
import java.util.ArrayList;

public class ShiftingDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("Ann", "Bob", "Cal", "Dee"));
        names.add(1, "Zoe"); // Bob, Cal, Dee all shift right by one
        System.out.println(names);

        names.remove(0); // Zoe, Bob, Cal, Dee all shift left by one
        System.out.println(names);
    }
}
[Ann, Zoe, Bob, Cal, Dee]
[Zoe, Bob, Cal, Dee]

Operation

Time Complexity

Why

get(index) / set(index, value)

O(1)

Direct array offset lookup

add(value) at the end

O(1) amortized

Appends to the array; occasionally triggers a resize

add(index, value) in the middle

O(n)

Elements after the index must shift right

remove(index) in the middle

O(n)

Elements after the index must shift left

contains(value) / indexOf(value)

O(n)

Must scan elements sequentially

A Worked Example

Common ArrayList operations together

Java
import java.util.List;
import java.util.ArrayList;

public class ShoppingListDemo {
    public static void main(String[] args) {
        List<String> shoppingList = new ArrayList<>();
        shoppingList.add("Milk");
        shoppingList.add("Eggs");
        shoppingList.add("Bread");

        if (!shoppingList.contains("Butter")) {
            shoppingList.add("Butter");
        }

        shoppingList.remove("Eggs"); // remove by value
        System.out.println(shoppingList);

        for (String item : shoppingList) {
            System.out.println("- " + item);
        }
    }
}
[Milk, Bread, Butter]
- Milk
- Bread
- Butter
When ArrayList Is the Right Default
Tip
Use ArrayList as your default List implementation for most use cases. It gives excellent, cache-friendly performance for reading and appending, which is the access pattern the overwhelming majority of code actually needs. Reach for LinkedList only when you specifically need fast insertion and removal at both ends of a very large list — the dedicated LinkedList page explains exactly when that trade-off pays off.
Note
Declare the variable as List<String> rather than ArrayList<String> even when you know you're using an ArrayList — it keeps your code flexible if you ever need to switch implementations later, without changing anything beyond the single line that creates the object.