Skip to content
Java collections 7 min read

List Interface

List is one of the most-used interfaces in Java — it models an ordered sequence of elements where duplicates are allowed and every element has an index. Whether you need a shopping cart, a to-do queue, or a buffer of log entries, List is usually the first tool you reach for.

What Makes a List a List?

List extends Collection (which extends Iterable), adding index-based access on top of the standard bag-of-elements contract. The three defining characteristics are:

  • Ordered — elements stay in the order you insert them (insertion order by default).
  • Index-based — you can get(0), set(2, value), or remove(3) by position.
  • Duplicates allowed — the same object (or two objects that are equals) can appear multiple times.

Note: List is an interface, not a class. You always create a concrete implementation: ArrayList, LinkedList, Vector, or CopyOnWriteArrayList. For most cases, ArrayList is the right default.

Common Implementations at a Glance

ImplementationBacked ByRandom AccessInsert/Delete at MiddleThread-Safe
ArrayListDynamic arrayO(1)O(n)No
LinkedListDoubly linked listO(n)O(1) at nodeNo
VectorDynamic arrayO(1)O(n)Yes (synchronized)
CopyOnWriteArrayListArray (copy-on-write)O(1)O(n)Yes (lock-free reads)

See ArrayList and LinkedList for full deep dives into the two most common choices.

Creating a List

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

public class CreateList {
    public static void main(String[] args) {
        // Most common: ArrayList
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");

        // LinkedList — same interface, different internals
        List<Integer> numbers = new LinkedList<>();
        numbers.add(10);
        numbers.add(20);
        numbers.add(30);

        System.out.println(fruits);   // [Apple, Banana, Cherry]
        System.out.println(numbers);  // [10, 20, 30]
    }
}

Output:

[Apple, Banana, Cherry]
[10, 20, 30]

Tip: Always declare the variable as List<T> (the interface), not ArrayList<T>. This keeps your code flexible — if you later switch implementations, only the new line changes.

Core List Methods

The List interface adds the following methods on top of the standard Collection API:

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

public class ListMethods {
    public static void main(String[] args) {
        List<String> lang = new ArrayList<>();

        // --- Adding elements ---
        lang.add("Java");          // append
        lang.add("Python");
        lang.add(1, "Kotlin");     // insert at index 1

        System.out.println(lang);  // [Java, Kotlin, Python]

        // --- Accessing elements ---
        System.out.println(lang.get(0));  // Java
        System.out.println(lang.size());  // 3

        // --- Updating ---
        lang.set(2, "Scala");
        System.out.println(lang);  // [Java, Kotlin, Scala]

        // --- Removing ---
        lang.remove(1);            // remove by index
        lang.remove("Scala");      // remove by value (first match)
        System.out.println(lang);  // [Java]

        // --- Searching ---
        lang.add("Java");
        lang.add("Go");
        lang.add("Java");
        System.out.println(lang.indexOf("Java"));     // 0 (first)
        System.out.println(lang.lastIndexOf("Java")); // 2 (last)
        System.out.println(lang.contains("Go"));      // true
    }
}

Output:

[Java, Kotlin, Python]
Java
3
[Java, Kotlin, Scala]
[Java]
0
2
true

Iterating Over a List

There are several ways to walk through a List, each with its own trade-offs.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class ListIteration {
    public static void main(String[] args) {
        List<String> colors = new ArrayList<>(List.of("Red", "Green", "Blue"));

        // 1. Enhanced for-loop (cleanest for read-only)
        for (String c : colors) {
            System.out.print(c + " ");
        }
        System.out.println();

        // 2. Index-based for-loop (useful when you need the index)
        for (int i = 0; i < colors.size(); i++) {
            System.out.print(i + ":" + colors.get(i) + " ");
        }
        System.out.println();

        // 3. Iterator (safe removal during iteration)
        Iterator<String> it = colors.iterator();
        while (it.hasNext()) {
            if (it.next().equals("Green")) it.remove(); // safe!
        }
        System.out.println(colors);

        // 4. forEach with lambda (Java 8+)
        colors.forEach(c -> System.out.print(c.toLowerCase() + " "));
        System.out.println();

        // 5. ListIterator — bidirectional, supports set() and add()
        ListIterator<String> lit = colors.listIterator(colors.size());
        while (lit.hasPrevious()) {
            System.out.print(lit.previous() + " "); // reverse order
        }
    }
}

Output:

Red Green Blue 
0:Red 1:Green 2:Blue 
[Red, Blue]
red blue 
Blue Red 

Warning: Never modify a List directly (e.g., list.remove() or list.add()) while iterating with a for-each loop or Iterator — you’ll get a ConcurrentModificationException. Use Iterator.remove() or collect changes and apply after.

Sorting a List

List integrates seamlessly with Collections.sort() and the sort() method added in Java 8.

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

public class SortList {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>(List.of(5, 1, 4, 2, 3));

        // Natural order (Comparable)
        Collections.sort(nums);
        System.out.println(nums);  // [1, 2, 3, 4, 5]

        // Reverse order
        nums.sort(Comparator.reverseOrder());
        System.out.println(nums);  // [5, 4, 3, 2, 1]

        // Sort strings by length
        List<String> words = new ArrayList<>(List.of("banana", "fig", "apple", "kiwi"));
        words.sort(Comparator.comparingInt(String::length));
        System.out.println(words); // [fig, kiwi, apple, banana]
    }
}

Output:

[1, 2, 3, 4, 5]
[5, 4, 3, 2, 1]
[fig, kiwi, apple, banana]

Head to Comparable and Comparator to understand exactly how custom ordering works.

Sublists and Bulk Operations

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

public class BulkOps {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>(List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));

        // subList — a live view (changes reflect in original)
        List<Integer> sub = nums.subList(2, 6);  // indices 2..5
        System.out.println(sub);  // [2, 3, 4, 5]

        sub.clear();              // removes elements 2-5 from nums too
        System.out.println(nums); // [0, 1, 6, 7, 8, 9]

        // addAll / removeAll / retainAll
        List<String> a = new ArrayList<>(List.of("x", "y", "z"));
        List<String> b = List.of("y", "z", "w");

        a.retainAll(b);           // keeps only elements in both
        System.out.println(a);    // [y, z]
    }
}

Output:

[2, 3, 4, 5]
[0, 1, 6, 7, 8, 9]
[y, z]

Warning: subList() returns a view, not a copy. Structural changes to the original list after calling subList() make the sublist unusable and will throw ConcurrentModificationException.

Factory Methods — Immutable Lists (Java 9+)

Java 9 introduced List.of() and Java 10 added List.copyOf(). These return compact, immutable lists that are great for constants and test data.

import java.util.List;

public class ImmutableList {
    public static void main(String[] args) {
        List<String> days = List.of("Mon", "Tue", "Wed", "Thu", "Fri");
        System.out.println(days.get(2));  // Wed
        System.out.println(days.size());  // 5

        // days.add("Sat");  // throws UnsupportedOperationException!

        // To get a mutable copy:
        List<String> mutable = new ArrayList<>(days);
        mutable.add("Sat");
        System.out.println(mutable);
    }
}

Output:

Wed
5
[Mon, Tue, Wed, Thu, Fri, Sat]

Tip: List.of() does not allow null elements and throws NullPointerException if you try. If you need nulls, use new ArrayList<>() and add them explicitly.

Under the Hood

Understanding what happens inside helps you write faster code and avoid surprises.

ArrayList Internals

ArrayList wraps a plain Object[] array. When you add an element beyond its current capacity, Java allocates a new array at 1.5× the old size and copies everything over. This amortises the cost so that add() at the end is O(1) amortised, even though occasional resizes are O(n).

Initial capacity: 10
After 11th add: new array of size 15, copy 10 elements
After 16th add: new array of size 22, copy 15 elements

If you know roughly how many elements you’ll store, pass an initial capacity to the constructor — new ArrayList<>(1000) — to avoid repeated resizing.

LinkedList Internals

LinkedList uses a doubly-linked list of Node objects. Each node holds a reference to its predecessor, its successor, and the stored element. This means:

  • Insert/delete at the head or tail — O(1).
  • Insert/delete at an arbitrary position by index — O(n) to traverse to that node first.
  • Random access (get(i)) — O(n) because traversal is needed.

For read-heavy workloads with random access, ArrayList wins clearly. For workloads that heavily insert or delete at both ends, consider ArrayDeque (from Deque & ArrayDeque) before LinkedList — it’s usually faster due to better cache behaviour.

Fail-Fast Iterators

ArrayList and LinkedList track structural modifications with an internal modCount counter. When you create an Iterator, it captures the current modCount. On each next() call the iterator checks that modCount hasn’t changed — if it has, it throws ConcurrentModificationException. This is a fail-fast policy designed to catch bugs early rather than silently produce incorrect results.

For genuinely concurrent code, look at the concurrent collections — in particular CopyOnWriteArrayList.

Choosing the Right List Implementation

ScenarioBest Choice
General-purpose, mostly reads + end-appendsArrayList
Frequent inserts/deletes in the middleLinkedList (but benchmark first)
Need a stack or double-ended queueArrayDeque
Legacy synchronized codeVector (or wrap with Collections.synchronizedList)
Concurrent read-heavy, rare writesCopyOnWriteArrayList
Fixed data / constantsList.of() (immutable)
  • ArrayList — the most-used List implementation, with resizing details and performance tips
  • LinkedList — doubly-linked implementation and when it actually beats ArrayList
  • ArrayList vs LinkedList — side-by-side performance comparison
  • Collections Framework — the big picture: interfaces, hierarchy, and choosing the right type
  • Comparable and Comparator — powering custom sort orders on your lists
  • Stream API — process and transform List contents with filter, map, and reduce
Last updated June 13, 2026
Was this helpful?