"Textbooks teach linked lists right after arrays and imply they're equals. They're not equals — and the surprising truth is the array wins far more often than the Big-O table suggests."
The Cost Table, Side by Side
Put them next to each other and the trade is clear:
- Index the i-th element: array O(1) · linked list O(n).
- Insert/delete at a known node: array O(n) (shift) · linked list O(1) (rewire).
- Insert/delete at the end: array amortized O(1) · linked list O(1) with a tail pointer.
- Search by value: both O(n).
- Memory per element: array = just the value · linked list = value + one or two pointers (more overhead).
On paper, the linked list looks great for insert-heavy workloads. So why is it almost never the right default?
The Plot Twist: Cache Locality
Big-O counts operations but ignores how long each operation actually takes — and on real hardware, those times are wildly unequal. Array elements sit contiguously, so when the CPU reads one it prefetches the neighbors into cache; a scan flies. Linked-list nodes are scattered at random addresses, so every node.next hop is a cache miss — the CPU stalls waiting for memory it couldn't predict. The result: a linear scan of a contiguous array routinely beats walking a linked list of the same length by 3–10×, despite identical O(n). The constant factor Big-O throws away is, here, the whole story.
So When IS a Linked List Right?
Linked lists earn their place in specific situations — usually as a component of something bigger, not as a list you index:
- LRU cache — a hash map (for O(1) find) plus a doubly linked list (for O(1) move-to-front and evict-from-back). This combo is the canonical use, and you'll meet it in the wild.
- Deques — Python's
collections.dequeuses linked blocks for O(1) at both ends. - Adjacency lists — how graphs store edges (next track but one).
- Undo/redo, ring buffers, free lists — anywhere you splice and stable references matter more than indexing.