When does ArrayList win over LinkedList in Java, and why is the answer almost always ArrayList?
ArrayList wins in almost every real case, and the reason is memory locality rather than raw operation counts. An ArrayList keeps its elements in one contiguous block of memory, so the CPU can prefetch them and the whole array fits in cache lines. A LinkedList scatters each element across the heap, with each node holding a pointer to the next, so every traversal is a chain of cache misses. That difference dwarfs the constant-factor advantages LinkedList has for a handful of operations.
Where LinkedList does have theoretical advantages is insertion and removal at a known position. Finding the position still costs O(n) because you have to walk the list, but once you are there, the actual insertion is O(1) because you just repoint two nodes. An ArrayList, by contrast, has to shift every subsequent element to make room, which is O(n).
And that advantage is theoretical in practice. To use it you need to be at the right position already, which usually means you have to search for it — and the search in a LinkedList is far slower than the search in an ArrayList because of the cache behaviour.
There is one more thing worth knowing: ArrayList amortises the cost of growth. When it runs out of capacity it allocates a bigger array — usually 1.5 times the size — and copies everything across, so the total cost of n appends is O(n) rather than O(n squared).
The standard advice: use ArrayList unless you have a specific, measured reason not to. If you need frequent insertion in the middle, prefer a different structure entirely — a gap buffer, a tree, or a deque — rather than reaching for LinkedList.
The same operations on both collections show why ArrayList is the default choice — random access is O(1) and there is no per-node object overhead:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> array = new ArrayList<>();
List<Integer> linked = new LinkedList<>();
for (int i = 0; i < 100_000; i++) {
array.add(i);
linked.add(i);
}
// Random access: ArrayList jumps straight to the index.
long t0 = System.nanoTime();
int sum = 0;
for (int i = 0; i < 100_000; i++) {
sum += array.get(i);
}
long arrayMs = (System.nanoTime() - t0) / 1_000_000;
// LinkedList has to walk the list to reach the same index.
t0 = System.nanoTime();
sum = 0;
for (int i = 0; i < 100_000; i++) {
sum += linked.get(i);
}
long linkedMs = (System.nanoTime() - t0) / 1_000_000;
System.out.println("sum: " + sum);
System.out.println("ArrayList get loop: " + arrayMs + " ms");
System.out.println("LinkedList get loop: " + linkedMs + " ms");
}
}