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), orremove(3)by position. - Duplicates allowed — the same object (or two objects that are
equals) can appear multiple times.
Note:
Listis an interface, not a class. You always create a concrete implementation:ArrayList,LinkedList,Vector, orCopyOnWriteArrayList. For most cases,ArrayListis the right default.
Common Implementations at a Glance
| Implementation | Backed By | Random Access | Insert/Delete at Middle | Thread-Safe |
|---|---|---|---|---|
ArrayList | Dynamic array | O(1) | O(n) | No |
LinkedList | Doubly linked list | O(n) | O(1) at node | No |
Vector | Dynamic array | O(1) | O(n) | Yes (synchronized) |
CopyOnWriteArrayList | Array (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), notArrayList<T>. This keeps your code flexible — if you later switch implementations, only thenewline 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
Listdirectly (e.g.,list.remove()orlist.add()) while iterating with a for-each loop orIterator— you’ll get aConcurrentModificationException. UseIterator.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 callingsubList()make the sublist unusable and will throwConcurrentModificationException.
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 allownullelements and throwsNullPointerExceptionif you try. If you need nulls, usenew 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
| Scenario | Best Choice |
|---|---|
| General-purpose, mostly reads + end-appends | ArrayList |
| Frequent inserts/deletes in the middle | LinkedList (but benchmark first) |
| Need a stack or double-ended queue | ArrayDeque |
| Legacy synchronized code | Vector (or wrap with Collections.synchronizedList) |
| Concurrent read-heavy, rare writes | CopyOnWriteArrayList |
| Fixed data / constants | List.of() (immutable) |
Related Topics
- ArrayList — the most-used
Listimplementation, 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
Listcontents with filter, map, and reduce