ArrayList vs LinkedList: When to Use Each
ArrayList and LinkedList both implement the List interface, but they are built on fundamentally different data structures with very different performance characteristics. ArrayList is backed by a resizable array: random access is O(1) but insertions in the middle require shifting elements. LinkedList is backed by a doubly-linked list of nodes: middle insertions are O(1) once the position is found, but random access requires walking the list. In practice, ArrayList wins almost every benchmark due to CPU cache friendliness, and LinkedList is rarely the right choice.
How ArrayList Works Internally
ArrayListArrayListadd()
The key performance advantage of an array is cache locality. Array elements are stored in contiguous memory. When the CPU loads one element, the memory controller fetches a whole cache line (typically 64 bytes) of adjacent elements along with it. Iterating an ArrayList
You can pre-size an ArrayListnew ArrayList<>(initialCapacity)
import java.util.ArrayList;
ArrayList<String> list = new ArrayList<>(100); // pre-allocate for 100 elements
list.add("Alice"); // O(1) amortised โ appends to end
list.add("Bob");
list.add(0, "Zara"); // O(n) โ shifts Alice, Bob one position right
System.out.println(list.get(1)); // O(1) โ direct array access โ "Alice"
System.out.println(list.size()); // 3
list.remove(0); // O(n) โ shifts remaining elements left
System.out.println(list); // [Alice, Bob]
How LinkedList Works Internally
LinkedListLinkedList
Adding to the front or back of a LinkedListLinkedListDeque
The weakness is random access and cache misses. Calling get(50)LinkedListget()ArrayList.get()
import java.util.LinkedList;
LinkedList<String> list = new LinkedList<>();
list.addFirst("Alice"); // O(1) โ update head pointer
list.addLast("Bob"); // O(1) โ update tail pointer
list.addFirst("Zara"); // O(1) โ update head pointer
System.out.println(list.getFirst()); // O(1) โ "Zara"
System.out.println(list.getLast()); // O(1) โ "Bob"
System.out.println(list.get(1)); // O(n) โ walks to position 1 โ "Alice"
list.removeFirst(); // O(1) โ detach head node
list.removeLast(); // O(1) โ detach tail node
System.out.println(list); // [Alice]
Time Complexity Comparison
The table below shows the algorithmic complexity of common operations for both implementations. Note that Big-O alone does not tell the full story: constant factors and cache effects matter enormously in practice, which is why ArrayListLinkedList
| Operation | ArrayList | LinkedList |
|---|---|---|
get(index) | O(1) | O(n) |
add(element) | O(1) amortised | O(1) |
add(0, element) | O(n) | O(1) |
add(i, element) | O(n) | O(n) ยน |
remove(index) | O(n) | O(n) ยน |
removeFirst()removeLast() | O(n) / O(1) | O(1) / O(1) |
| Sequential iteration | Fast (cache hits) | Slow (cache misses) |
| Memory per element | ~4โ8 bytes (reference) | ~48 bytes (node object) |
ยน Finding the position takes O(n), but unlinking the node itself is O(1) once found. The total cost is still O(n).
Why ArrayList Wins in Practice
Even for operations where LinkedListArrayListArrayListLinkedListnext
Memory overhead is also significant. Each LinkedListprevnextLinkedListArrayList
The practical guidance from Java performance experts (including the JDK team) is: default to ArrayListLinkedList
// Prefer ArrayList for general-purpose use
List<String> list = new ArrayList<>();
// If you need frequent front-insertions or use it as a deque,
// use ArrayDeque instead of LinkedList
Deque<String> deque = new ArrayDeque<>();
deque.addFirst("first");
deque.addLast("last");
deque.pollFirst(); // O(1) โ faster than LinkedList due to cache locality
ArrayDequeLinkedListArrayDequeLinkedListWhen LinkedList Is the Right Choice
LinkedListListIterator
A second legitimate use is as a queue or deque when the list is also being modified by multiple threads using its built-in DequeLinkedBlockingDequejava.util.concurrent
| Use case | Best choice |
|---|---|
| General-purpose list | ArrayList |
| Read-heavy, random access | ArrayList |
| Append to end frequently | ArrayList |
| Queue / deque (add/remove at ends) | ArrayDeque |
| Many insertions at iterator cursor | LinkedListListIterator |
| Thread-safe queue | LinkedBlockingDeque |