skip to content

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%

answer

  1. Cache line ~64 bytes; RAM miss ~100s of cycles
  2. LinkedList node = value + next + prev + header ≈ 3x memory
  3. ArrayList contiguous → prefetch-friendly; LinkedList scattered → pointer chasing
  4. Big-O hides constant factors and assumes uniform memory cost
  5. Prefer ArrayDeque over LinkedList

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.

solid answer

~50 s

ArrayList stores elements in one contiguous array, so memory overhead is small (just the references plus some spare capacity) and iteration is cache-friendly — the CPU prefetches neighboring elements that sit next to each other in RAM. LinkedList allocates a separate node object per element, each carrying the value plus next and previous references plus the object header, roughly tripling memory. Worse, those nodes live at scattered heap addresses, so walking the list causes frequent cache misses and pointer chasing. Modern CPUs are far slower at fetching from main memory than from cache, so even a plain O(n) iteration over a LinkedList can be several times slower than over an ArrayList. This is why Big-O alone misleads: two O(n) operations can differ enormously in wall-clock time due to constant factors and locality. It is the main reason ArrayList is the practical default and ArrayDeque is preferred over LinkedList for queues.

go deeper

for a junior

Knows LinkedList uses more memory because each element is a separate linked object, and that ArrayList is generally faster.

for a middle

Quantifies the per-node overhead (value + two pointers + header) and explains that scattered nodes hurt iteration speed.

for a senior

Explains cache lines, prefetch, pointer chasing, and why constant factors/locality make two O(n) operations differ greatly in wall-clock time; recommends ArrayDeque.

for a principal

Reasons about allocation/GC pressure, footprint at scale, and hardware-aware data-structure choice; sets conventions and insists on measurement with realistic sizes and JIT-aware benchmarks.

## Background: how memory and the CPU cache work A modern CPU runs far faster than main memory (RAM) can feed it. To cope, it keeps small, fast **caches** between itself and RAM. When the CPU reads an address, it pulls in a whole **cache line** (typically 64 bytes — several adjacent elements) at once, betting you'll soon need the neighbors too. This bet is **spatial locality**. It also **prefetches** ahead when it detects a sequential scan. A read served from cache costs a few cycles; a **cache miss** that goes to RAM can cost hundreds. So *where* data sits in memory often dominates performance more than the abstract operation count. ## ArrayList memory layout `ArrayList` is backed by a single contiguous array of references. For `N` elements it holds one array object plus `N` references (4 or 8 bytes each) plus a bit of **spare capacity** (growth headroom, since it grows in ~1.5x jumps). Overhead is minimal and predictable. Because the references are packed side by side, iterating reads consecutive memory: each cache line brings in several elements, prefetch kicks in, and misses are rare. (Note: the *referenced objects* themselves may still be scattered on the heap; what's contiguous is the reference array — already a big win, and for primitives-as-objects or `record`-heavy data the locality benefit is real.) ## LinkedList memory layout `LinkedList` is a chain of `Node` objects. Each node stores: the element reference, a **next** reference, a **prev** reference, plus the JVM **object header** (12-16 bytes for bookkeeping like the class pointer and mark word). So a node costs roughly the element reference plus ~24-40 bytes of pure overhead — often **~3x** the memory of the equivalent ArrayList slot. Nodes are allocated independently and end up at **scattered heap addresses**. Traversal is **pointer chasing**: read this node, follow its `next` pointer to an unrelated address, almost certainly a cache miss, repeat. Prefetch can't help because the next address isn't predictable. The result: even straightforward iteration — same O(n) as ArrayList — runs several times slower. ## Why this beats Big-O in practice Big-O describes how cost **scales** with size, hiding **constant factors** and assuming all memory access is uniform-cost. Real hardware violates that assumption brutally. Two O(n) loops can differ 5-10x because one streams contiguous memory (cache hits) and the other chases pointers (cache misses). LinkedList's theoretical O(1) middle-relink advantage is usually swamped by (a) the O(n) traversal to reach the spot and (b) cache misses during that traversal. Meanwhile its memory bloat increases allocation, GC pressure, and footprint. ## Practical consequences - **Default to ArrayList** — smaller, faster to iterate, friendlier to the cache and GC. - **Prefer `ArrayDeque` over `LinkedList`** for stacks/queues/deques: it's array-backed (good locality) and has no per-node overhead. - **Reach for LinkedList only** when you specifically need O(1) insert/remove at a position you already hold (e.g. iterator-based removal) and you've measured a benefit. - **Measure, don't assume**: profile with realistic data sizes; microbenchmarks must account for JIT warmup and GC.

  • ArrayList's reference array is contiguous, but the referenced objects can still be scattered. Does locality still help?
    Yes. Streaming the contiguous reference array is itself cache-friendly, and for compact element types the win is large. Even when target objects are scattered, ArrayList avoids LinkedList's extra prev/next pointer hops, so it still iterates faster and uses far less memory.
  • Why is ArrayDeque usually a better choice than LinkedList for a queue?
    ArrayDeque is backed by a circular array: O(1) amortized add/remove at both ends with no per-element node overhead and good cache locality, so it's faster and leaner than LinkedList for almost all FIFO/LIFO/deque use.

saying these in an interview costs you the question

  • Treating two O(n) operations as equally fast
  • Ignoring the JVM object header and per-node pointer cost
  • Claiming LinkedList saves memory
  • Assuming cache locality is negligible on modern hardware
  • Recommending LinkedList for queues instead of ArrayDeque

context