skip to content

ArrayList vs LinkedList

Random access versus pointer relinking, per-node memory overhead, and cache locality — which in practice makes ArrayList win far more often than the Big-O table suggests. Interviewers ask this constantly and want the practical answer, not just the complexities.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

What is the fundamental difference between ArrayList and LinkedList in Java, and how does it affect their performance characteristics?

level: juniorimportance: must knowfreq 85%

answer

  1. Array (contiguous, indexable) vs nodes (scattered, linked)
  2. get(i): O(1) vs O(n)
  3. ArrayList middle insert = shift; LinkedList middle insert = traverse then relink
  4. LinkedList = 2 extra pointers/node + bad cache locality
  5. Default to ArrayList

basics

~20 s

ArrayList stores elements in a resizable array, so reading any element by index is instant. LinkedList stores elements as separate nodes connected by links, so reading by index means walking the chain. ArrayList is faster for most uses.

solid answer

~40 s

Both implement the List interface but use opposite internal structures. ArrayList is backed by a contiguous array, giving O(1) random access by index (get/set) and good cache locality, but inserting or removing in the middle requires shifting all later elements (O(n)). LinkedList is a doubly linked list of nodes, each holding the value plus references to the previous and next node; it has O(1) insert/remove at the ends or once you hold a node, but get(index) is O(n) because it must traverse. LinkedList also uses more memory per element (two extra pointers per node) and has poor cache locality since nodes are scattered on the heap. In practice ArrayList is the default choice; LinkedList rarely wins because traversal cost and pointer overhead usually outweigh its theoretical insertion advantage.

go deeper

for a junior

Knows ArrayList uses an array and LinkedList uses linked nodes, and that ArrayList's get by index is fast while LinkedList's is slow; defaults to ArrayList.

for a middle

States the Big-O table correctly, explains the shift-vs-relink tradeoff, and notes memory overhead and that append is amortized O(1).

for a senior

Explains why ArrayList wins even at middle insertion (traversal cost + cache locality + constant factors), and when LinkedList genuinely helps (iterator removal, deque ends), suggesting ArrayDeque as a better deque.

for a principal

Frames the choice around real workload profiles and hardware (cache lines, prefetch, allocation pressure/GC), can reason about amortized analysis, and sets team conventions defaulting to ArrayList while justifying rare exceptions with measurement.

## What a List is In Java, `List` is an interface — a contract that says "an ordered collection you can access by integer position (index)." `ArrayList` and `LinkedList` are two **implementations** of that same contract. They behave identically from the outside (`add`, `get`, `remove`, `size`) but store the data completely differently inside, which is why their speed differs. ## ArrayList: backed by an array An **array** is a single block of memory holding elements one after another, like numbered boxes in a row. `ArrayList` wraps a resizable array. - **Random access — `get(i)` / `set(i)`** is **O(1)** (constant time): the computer computes the memory address directly as `base + i * elementSize` and jumps straight there. No matter how large the list, fetching element 5 or element 5,000,000 costs the same. - **Insert/remove in the middle** is **O(n)** (linear time): to insert at position `i`, every element from `i` onward must be **shifted** one slot to make room (or to close the gap on removal). With a million elements, inserting near the front moves ~a million elements. - **Append at the end** is **amortized O(1)**: usually instant, but when the array fills up, `ArrayList` allocates a bigger array (typically ~1.5x) and copies everything over. That occasional copy is spread ("amortized") over many cheap appends, so the average stays constant. - **Memory**: just the array plus a little slack capacity. No per-element overhead beyond the references themselves. - **Cache locality**: excellent. Because elements sit contiguously in memory, the CPU's cache loads neighbors together, so iterating is very fast in practice. ## LinkedList: a chain of nodes A **node** is a small object holding one value plus references (pointers) to other nodes. Java's `LinkedList` is **doubly linked**: each node points to both the **next** and the **previous** node. The list keeps references to the first and last nodes. - **Random access — `get(i)`** is **O(n)**: there is no formula for "where is element i." You must start at the head (or tail, whichever is closer) and **walk** the chain `i` steps. Element 5,000,000 requires five million hops. - **Insert/remove once you already hold the node** is **O(1)**: you just rewire a few pointers (relink the neighbors). This is its one structural strength — e.g. removing via an `Iterator` during traversal. - **Insert/remove at the ends** is **O(1)**: it has direct references to first and last, which is why it's a natural `Deque`/queue. - **Memory**: high overhead. Every element needs a separate node object, and each node carries **two extra references** (prev + next) plus object header bloat — often ~3x the memory of an `ArrayList` of the same data. - **Cache locality**: poor. Nodes are allocated at scattered heap addresses, so the CPU cache can't prefetch neighbors; even plain iteration is slower than `ArrayList` despite both being O(n). ## The crucial subtlety: insertion in the middle People often say "LinkedList is better for inserting in the middle." The pointer **relink** itself is O(1) — but **finding** the middle position is O(n) for LinkedList (you must traverse to it). For ArrayList, finding is O(1) but the **shift** is O(n). So both are O(n) for an arbitrary-position insert; ArrayList's O(n) is a fast contiguous `System.arraycopy` (memory block move), while LinkedList pays an O(n) traversal plus cache misses. That's why ArrayList usually still wins even at the task LinkedList supposedly excels at — unless you already hold the position (e.g. an iterator). ## Big-O summary | Operation | ArrayList | LinkedList | |---|---|---| | `get(i)` / `set(i)` | O(1) | O(n) | | `add` at end | amortized O(1) | O(1) | | `add`/`remove` at front | O(n) | O(1) | | `add`/`remove` middle (by index) | O(n) (shift) | O(n) (traverse) + O(1) relink | | remove via iterator at cursor | O(n) (shift) | O(1) | | memory per element | low | high (2 pointers/node) | | cache locality | good | poor | ## Why ArrayList is the default For the overwhelming majority of real workloads — build a list, iterate it, look things up by index — ArrayList is faster and leaner. Modern CPUs reward contiguous memory heavily (cache), so even LinkedList's theoretical edges rarely materialize. The standard advice: **default to ArrayList; reach for LinkedList only when you genuinely need a queue/deque with heavy add/remove at both ends, or O(1) removal while iterating.** Even then, `ArrayDeque` is usually a better deque than `LinkedList`.

  • If both an arbitrary-index insert are O(n), why does ArrayList usually still beat LinkedList at it?
    ArrayList's O(n) is a single contiguous System.arraycopy (cache-friendly block move), while LinkedList must traverse node-by-node to reach the index (cache misses) before its O(1) relink. The constant factors and cache behavior favor ArrayList.
  • When does LinkedList's O(1) insertion actually pay off?
    When you already hold the position — e.g. removing/inserting via an Iterator during traversal, or pushing/popping at the ends as a queue/deque — so you skip the O(n) search entirely.

saying these in an interview costs you the question

  • Claiming LinkedList is faster for middle insertion overall — finding the spot is still O(n) traversal
  • Saying ArrayList add() is O(n) — it's amortized O(1) at the end
  • Forgetting LinkedList get(i) is O(n), not O(1)
  • Ignoring cache locality and pretending only Big-O matters
  • Recommending LinkedList as a general default

context

open as a page

Given a real-world scenario, how do you decide between ArrayList and LinkedList — and why is ArrayList almost always the right default?

level: middleimportance: must knowfreq 70%

basics

~20 s

Use ArrayList by default — it's faster for reading by index and uses less memory. Only consider LinkedList if you constantly add or remove at the very start/end or remove items while looping with an iterator. Even then, ArrayDeque is usually better.

open as a page

Explain why ArrayList's add() at the end is described as 'amortized O(1)' and what happens when the backing array fills up.

level: middleimportance: should knowfreq 50%

basics

~20 s

ArrayList keeps a fixed-size array inside. Adding is instant until it's full; then it makes a bigger array (about 1.5x) and copies everything over. That copy is occasional and spread across many cheap adds, so on average each add is constant time.

open as a page

Why is iterating a LinkedList with a for-i index loop a performance trap, and what's the correct way to traverse it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

In a LinkedList, get(i) has to walk from the start each time, so a loop calling get(0), get(1), get(2)... re-walks the chain repeatedly and becomes very slow (O(n^2)). Use an enhanced for-loop or iterator instead, which walks the chain just once.

open as a page

Compare the memory overhead and cache locality of ArrayList and LinkedList. Why do these often matter more than Big-O in practice?

level: seniorimportance: should knowfreq 55%

basics

~20 s

LinkedList uses much more memory because every element is a separate object with two extra links. Its elements are scattered in memory, so the CPU cache can't help, making it slower in real use even when Big-O looks equal.

open as a page