ArrayList

Su Suriya Ravichandran Updated 14 Nov 2026
7 min read ·Lesson 71 of 133

ArrayList is a resizable list backed by an array. It keeps elements in insertion order, allows duplicates, gives fast access by index, and grows automatically as elements are added. It is the most widely used collection in Java, and the default choice whenever a program needs an ordered list whose size is not known in advance.

Why ArrayList Exists

Arrays have a fixed length. To add an element to a full array you must create a bigger one and copy everything, and to remove an element you must shift the others by hand. ArrayList does all of this internally:

String[] arr = new String[2];
arr[0] = "Ada";
arr[1] = "Linus";
// arr[2] = "Grace";          // ArrayIndexOutOfBoundsException: the array is full

List<String> list = new ArrayList<>();
list.add("Ada");
list.add("Linus");
list.add("Grace");            // grows automatically

Creating an ArrayList

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

List<String> names = new ArrayList<>();                    // empty
List<Integer> scores = new ArrayList<>(1000);              // empty, room for 1000 before growing
List<String> copy = new ArrayList<>(List.of("a", "b"));    // copy of another collection

Declare the interface type

The variable is usually declared as List<String>, not ArrayList<String>:

List<String> names = new ArrayList<>();

Code that uses names then depends only on the List interface. Switching to another implementation later changes one line. Method parameters and return types should also use List for the same reason.

Generics and the diamond operator

<String> is a type argument: this list holds only String values, and the compiler enforces it.

names.add("Ada");
names.add(42);               // compile error: int cannot be converted to String
String first = names.get(0); // no cast needed

new ArrayList<>() uses the diamond operator: the compiler infers <String> from the variable's declared type.

Lists of primitives

Collections hold objects only. For numbers, use the wrapper classes; Java converts automatically (autoboxing):

List<Integer> ages = new ArrayList<>();
ages.add(30);                // int 30 is boxed to Integer
int a = ages.get(0);         // unboxed back to int

List<int> does not compile. For very large amounts of numeric data, a primitive array (int[]) uses far less memory than a List<Integer>, since each Integer is a separate object.

Adding Elements

List<String> tasks = new ArrayList<>();
tasks.add("Write tests");              // append to the end
tasks.add("Fix bug");
tasks.add(0, "Review PR");             // insert at index 0; others shift right
tasks.addAll(List.of("Deploy", "Update docs"));

System.out.println(tasks);
// [Review PR, Write tests, Fix bug, Deploy, Update docs]

add(index, element) accepts any index from 0 to size(). An index of size() appends; anything larger throws IndexOutOfBoundsException.

Accessing and Updating

String first = tasks.get(0);                  // "Review PR"
String last = tasks.get(tasks.size() - 1);    // "Update docs"
tasks.set(2, "Fix login bug");                // replace; returns the old element
int count = tasks.size();                     // 5
boolean none = tasks.isEmpty();               // false

size() is the number of elements actually in the list (unlike an array's length, which is its capacity). Valid indexes are 0 to size() - 1.

Searching

tasks.contains("Deploy");        // true
tasks.indexOf("Deploy");         // 3, or -1 if absent
tasks.lastIndexOf("Deploy");     // last occurrence

These methods compare with equals, so they work correctly for String, wrapper classes, records, and any class that overrides equals. They scan the list from the start, taking time proportional to its size.

Removing Elements

tasks.remove("Deploy");          // removes the first matching element; returns true/false
tasks.remove(0);                 // removes by index; returns the removed element
tasks.removeIf(t -> t.startsWith("Update"));   // removes every element matching a condition
tasks.clear();                   // removes everything

The remove(int) versus remove(Object) trap

For a List<Integer>, remove is overloaded, and an int argument selects the index version:

List<Integer> nums = new ArrayList<>(List.of(10, 20, 30));
nums.remove(1);                       // removes index 1 → [10, 30]
nums.remove(Integer.valueOf(10));     // removes the value 10 → [30]

Wrap the value in Integer.valueOf(...) when you mean "remove this number."

Iterating

List<String> langs = new ArrayList<>(List.of("Java", "Bash", "C"));

for (String lang : langs) {                     // for-each
    System.out.println(lang);
}

for (int i = 0; i < langs.size(); i++) {        // by index
    System.out.println(i + ": " + langs.get(i));
}

langs.forEach(System.out::println);             // forEach with a method reference

Do not modify the list inside a for-each loop

for (String lang : langs) {
    if (lang.equals("Java")) {
        langs.remove(lang);       // ConcurrentModificationException on the next iteration
    }
}

The for-each loop uses an iterator, which detects that the list was changed by other code and, in most cases, fails fast with ConcurrentModificationException. The check is best-effort, not guaranteed: removing the second-to-last element (here "Bash") happens to end the loop silently without an exception, and the last element is simply never visited. Because the behavior depends on position, never rely on it. Use removeIf, or an explicit Iterator and its own remove() method:

langs.removeIf(lang -> lang.equals("Bash"));

Iterator<String> it = langs.iterator();
while (it.hasNext()) {
    if (it.next().length() < 2) it.remove();
}

Sorting and Other Bulk Operations

List<String> names = new ArrayList<>(List.of("Linus", "ada", "Grace"));

names.sort(null);                                   // natural order: [Grace, Linus, ada]
names.sort(String.CASE_INSENSITIVE_ORDER);          // [ada, Grace, Linus]
names.sort(Comparator.comparing(String::length));   // by length
names.replaceAll(String::toUpperCase);              // transform every element in place
Collections.reverse(names);
Collections.shuffle(names);

How ArrayList Works Internally

An ArrayList holds an internal array (its capacity) and a count of elements in use (its size):

size = 3, capacity = 10
[ "A" | "B" | "C" | null | null | null | null | null | null | null ]
  • add at the end stores into the next free slot. When the array is full, ArrayList allocates a larger one (about 1.5 times the old capacity) and copies the elements over. Because growth is proportional, the average cost of appending stays constant.
  • get(i) and set(i, x) read or write the internal array directly: constant time.
  • add(i, x) and remove(i) in the middle must shift every following element by one position, so they take time proportional to the number of elements after i.
  • contains and indexOf scan from the start.
OperationTime
get(i), set(i, x)O(1)
add(x) at the endO(1) on average
add(i, x), remove(i)O(n) (shifts elements)
contains, indexOf, remove(Object)O(n)

If you know roughly how many elements you will add, passing an initial capacity (new ArrayList<>(10_000)) avoids repeated regrowth. trimToSize() shrinks the internal array to the current size when memory matters.

Mutable and Unmodifiable Lists

List.of(...) and stream.toList() create unmodifiable lists. Calling add, remove, or set on them throws UnsupportedOperationException:

List<String> fixed = List.of("a", "b");
fixed.add("c");                        // UnsupportedOperationException

List<String> editable = new ArrayList<>(fixed);
editable.add("c");                     // fine

Wrapping in new ArrayList<>(...) gives a modifiable copy. Conversely, return List.copyOf(list) or Collections.unmodifiableList(list) from a method when callers should not be able to change your data.

Arrays.asList(array) is a third kind: a fixed-size list backed by the array. set works (and writes through to the array), but add and remove throw.

Converting Between Lists and Arrays

List<String> list = new ArrayList<>(List.of("x", "y", "z"));
String[] array = list.toArray(new String[0]);     // list → array
List<String> back = new ArrayList<>(Arrays.asList(array));   // array → modifiable list

Passing new String[0] tells toArray which array type to create.

ArrayList Versus Other Lists

ArrayListLinkedListArray
Access by indexFastSlow (walks the list)Fast
Add/remove at the endFastFastNot possible (fixed size)
Add/remove at the front or middleSlow (shifts)Fast once positionedNot possible
Memory per elementLowHigh (node objects)Lowest
PrimitivesBoxedBoxedDirect

In practice, ArrayList is the right choice for the large majority of lists. LinkedList is covered in the next lesson; for queue-like use at both ends, ArrayDeque is usually better than either.

Practical Example: Shopping Cart

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Cart {
    record Item(String name, double price, int qty) {
        double total() { return price * qty; }
    }

    private final List<Item> items = new ArrayList<>();

    void add(String name, double price, int qty) {
        for (int i = 0; i < items.size(); i++) {
            Item it = items.get(i);
            if (it.name().equals(name)) {
                items.set(i, new Item(name, price, it.qty() + qty));   // replace with updated quantity
                return;
            }
        }
        items.add(new Item(name, price, qty));
    }

    boolean remove(String name) {
        return items.removeIf(it -> it.name().equals(name));
    }

    double total() {
        double sum = 0;
        for (Item it : items) sum += it.total();
        return sum;
    }

    List<Item> view() {
        return List.copyOf(items);
    }

    public static void main(String[] args) {
        Cart cart = new Cart();
        cart.add("Notebook", 80, 3);
        cart.add("Pen", 12.5, 10);
        cart.add("Backpack", 1499, 1);
        cart.add("Pen", 12.5, 5);
        cart.remove("Notebook");

        List<Item> sorted = new ArrayList<>(cart.view());
        sorted.sort(Comparator.comparingDouble(Item::total).reversed());
        for (Item it : sorted) {
            System.out.printf("%-9s %3d x %8.2f = %9.2f%n", it.name(), it.qty(), it.price(), it.total());
        }
        System.out.printf("Total: %.2f (%d lines)%n", cart.total(), cart.view().size());
    }
}

Output:

Backpack    1 x  1499.00 =   1499.00
Pen        15 x    12.50 =    187.50
Total: 1686.50 (2 lines)

The cart stores Item records in an ArrayList. Adding an existing product finds it by index and uses set to replace it with a new record carrying the combined quantity, because records are immutable. removeIf deletes lines by condition without the risk of ConcurrentModificationException. view() returns an unmodifiable copy, so outside code cannot alter the cart directly; main makes its own modifiable copy to sort for display, leaving the cart's internal order untouched.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about ArrayList and get answers drawn from it.

Signed-in readers only.