Linked Lists
Nodes scattered through memory, stitched by pointers
Singly linked list · head → tail
init
Try it: Hit walk to chase the head pointer toward an index — count the hops. Then insert or delete at the same position — pointer rewires are O(1) but you still need to reach the spot.
Why it matters
Linked lists teach you to think in pointers - a fundamental concept in computer science. While often slower than arrays in practice, they unlock the door to understanding trees, graphs, and memory management.
The idea
Imagine a scavenger hunt. Each clue tells you where to find the next clue. You can't jump to clue #5 - you must follow the chain from the start.
That's a linked list. Each node contains data and a pointer to the next node.
The trade-off is inverted from arrays:
- Insert/delete at a known position: O(1) - just rewire the pointers
- Find a position by index: O(n) - must walk the chain
When you need to frequently insert/delete at the beginning or after a known node, linked lists win. When you need random access, arrays win.
Key takeaways
- Insert/delete at known position is O(1) - just change pointers
- Access by index is O(n) - must traverse from head
- No wasted space, but extra memory per node for pointers
- Good for frequent insertions at the front or after known nodes
Going deeper
Singly vs doubly vs circular
| Variant | Each node has | Notable property |
|---|---|---|
| Singly-linked | next only | smallest memory; can only walk forward |
| Doubly-linked | next + prev | O(1) delete given a node reference (no need to find prev); walk both ways |
| Circular (singly or doubly) | tail → next = head | round-robin scheduling; iterator never ends |
| XOR list (curiosity) | one pointer = prev XOR next | doubly-linked memory cost of singly. Breaks cache prefetch — academic. |
The killer feature of the doubly-linked list is the O(1) splice: given a node n you already have a pointer to, you can remove it from the list in two pointer writes:
n.prev.next = n.next;
n.next.prev = n.prev;
No traversal. This is why every LRU cache implementation under the sun uses a doubly-linked list backing a hash table: the hash table gives you O(1) lookup of the node by key; the doubly-linked list lets you O(1) move that node to the front on each access.
Circular variants underpin round-robin schedulers — every OS process scheduler that gives each runnable thread a time slice has a circular list somewhere. The "next" pointer from the last thread points back to the first; you just keep walking.
Memory overhead — the real numbers
A singly-linked list node holding an int32 on a 64-bit system:
[ 4 bytes data ][ 4 bytes padding ][ 8 bytes next pointer ] = 16 B
The data is 4 bytes, the storage is 16 bytes — 4× overhead per element. A doubly-linked list of the same data:
[ 4 bytes data ][ 4 bytes padding ][ 8 bytes next ][ 8 bytes prev ] = 24 B
6× overhead. And these nodes aren't sequential in memory — each is a separate heap allocation, so iterating the list is a cache-miss festival. The same int32s in a Vec<i32> pack into 4 bytes each, contiguous, prefetched by the CPU. For a million-element loop, the array can be 50–100× faster despite identical Big-O.
The lesson: linked lists are right when you genuinely need O(1) splice with a live node reference (LRU cache, OS scheduler, intrusive lists in kernel data structures). For everything else, prefer arrays — see the arrays lesson's cache-locality section.
Math details
- Time Complexity
Access by index: O(n)Insert at head: O(1)Insert after node: O(1)Delete node (given prev): O(1)Search: O(n)- Space
- Each node stores data + pointer(s)
Memory per node: sizeof(T) + sizeof(pointer)
Implementation
Linked List Node
struct Node<T> {
data: T,
next: Option<Box<Node<T>>>,
}
// Insert at head: O(1)
fn push_front(head: &mut Option<Box<Node<T>>>, data: T) {
let new_node = Box::new(Node {
data,
next: head.take(),
});
*head = Some(new_node);
}
When to Use
- Frequent insertions/deletions at front
- Unknown size, memory-constrained
- Building block for stacks, queues
- Understanding pointer manipulation
Practice
Three classic linked-list problems, each highlighting a different pointer pattern:
- Reverse a Linked List — easy · the three-pointer dance, the canonical pointer-manipulation exercise
- Detect a Cycle (Floyd's tortoise & hare) — medium ·
O(1)-space alternative to a visited-set - Merge Two Sorted Linked Lists — easy · splice nodes without allocating — the merge step of merge sort