Dataslope logoDataslope

Lists

ArrayList, LinkedList, the List interface, and how to choose between them — with the memory pictures that make the choice obvious

A list is an ordered, indexed sequence of elements. It is the most-used container in almost any program — the JCF interface List<E> is your default when you need to remember things in the order they arrived.

This page covers the List interface, its two main implementations (ArrayList and LinkedList), and the small handful of facts about their memory layout that explain almost every performance question you'll ever ask.

The List interface

The methods that distinguish a List from a plain Collection are all about position: get(i), add(i, x), remove(i), indexOf(x), subList(from, to). A list has seat numbers.

Code Block
Java 8 (Update 492)

ArrayList: a list backed by an array

ArrayList is the workhorse. Internally it holds an Object[] and a size counter. When the array fills up, it allocates a bigger one (typically 1.5× the current capacity) and copies.

That single picture explains every ArrayList performance fact:

OperationCostWhy
get(i), set(i, e)O(1)Pointer arithmetic into the array
add(e) (append)O(1) amortizedOne slot fill; occasionally double-and-copy
add(i, e) (insert middle)O(n)Shift everything after i
remove(i)O(n)Shift everything after i
contains(x), indexOf(x)O(n)Linear scan

LinkedList: a doubly-linked node chain

LinkedList is the other side of the coin. Each element is a node holding the value plus pointers to the previous and next nodes.

OperationCostWhy
get(i), set(i, e)O(n)Walk from head or tail
add(e) (append)O(1)Wire one new node onto the tail
add(0, e) (prepend)O(1)Wire one new node onto the head
addFirst / addLast (via Deque)O(1)Same
remove at a node you already holdO(1)Re-wire pointers
contains(x), indexOf(x)O(n)Linear scan

So LinkedList is O(1) at the ends and O(n) in the middle. It also implements Deque<E>, which is where most of its modern use comes from.

ArrayList vs LinkedList: when each wins

The honest short answer: ArrayList wins almost every real benchmark. The reason is hardware: arrays have contiguous memory, which the CPU's cache loves. Linked-list nodes are scattered all over the heap, costing a cache miss on every step.

The cases where LinkedList still wins:

  • You are doing many addFirst / removeFirst and need an equal guarantee — but ArrayDeque is almost always better for this.
  • You have an explicit reference to a node and need O(1) removal — but Java's LinkedList API doesn't expose nodes.

In modern Java code, LinkedList shows up much less often than you might guess from textbooks. Reach for ArrayList by default; reach for ArrayDeque when you genuinely need queue-like access at both ends.

Code Block
Java 8 (Update 492)

(Numbers depend on the JIT, but ArrayList typically beats LinkedList even for "pure append" because of cache locality and the cheaper node-less storage.)

Initial capacity: when you know the size

If you know the rough size of an ArrayList ahead of time, pass it to the constructor. Otherwise you pay for repeated grow() and Arrays.copyOf.

Code Block
Java 8 (Update 492)

Sublist: a view, not a copy

subList(from, to) returns a live view of the underlying list. Changes to the sublist affect the original — and structural changes to the original invalidate the sublist.

Code Block
Java 8 (Update 492)

This is enormously useful for slicing — and a frequent source of surprise for people who expect a copy.

Immutable list literals: List.of(...)

Java 9 added factory methods List.of(a, b, c) that produce immutable lists. They are cheap, type-safe, and exactly what you want for small fixed collections (especially as constants).

Code Block
Java 8 (Update 492)

For collections that are computed once and never mutated, this is strictly better than constructing an ArrayList and forgetting to seal it.

Removing while iterating: the right way

We met this in the iteration chapter, but it deserves a list-specific note: do not list.remove(...) inside a for-each loop. Use the iterator's remove(), or use removeIf (Java 8+):

Code Block
Java 8 (Update 492)

removeIf accepts a Predicate and does the safe iteration for you.

Practice

Challenge
Java 8 (Update 492)
A bounded recent-history list

Build a class RecentHistory<E> that remembers the most recent limit items added (older items fall off the front).

  • The constructor takes int limit. Assume limit >= 1.
  • void record(E item) appends item. If size exceeds limit, remove the oldest element.
  • List<E> items() returns the current list of items, oldest first.

Expected output:

[a, b, c]
[b, c, d]
[c, d, e]

Test your understanding

QuestionSelect one

Which operation is O(1) on ArrayList but O(n) on LinkedList?

Appending at the end

get(i) for arbitrary i

contains(x)

Removing the first element

QuestionSelect one

Why does ArrayList typically beat LinkedList in real-world benchmarks even for "linked-list-friendly" workloads?

The JVM secretly stores LinkedList nodes contiguously

Contiguous array memory is far more cache-friendly than scattered linked-list nodes, so the CPU spends much less time waiting on memory

LinkedList was deprecated in Java 11

ArrayList is implemented in native code

QuestionSelect one

What does subList(from, to) return?

A new independent ArrayList

A primitive int[]

A live view of the original list; mutations affect the backing list and structural changes to the backing list invalidate the view

An immutable list snapshot

On this page