Linked List vs Array: Differences and When to Use Each
Basics4 minInnovialabs editorial team
In short
An array stores elements side by side, so index access is O(1) and iteration is cache-friendly; a linked list stores scattered nodes joined by pointers, so it only wins when you insert or remove at a node you already hold.
The short answer
Use an array (a dynamic array: list in Python, Array in JavaScript) by default. It gives fast index access, fast iteration and compact memory.
Choose a linked list only when your main operation is inserting or removing elements at a position you already have a reference to, and you rarely need random access.
How they sit in memory
- Array — one continuous block of memory. Element
ilives atstart + i * size, so the address is computed instantly. - Linked list — separate nodes scattered across memory. Each node holds a value and a pointer to the next node (and to the previous one in a doubly linked list). To reach element
i, you walkipointers from the head.
A dynamic array keeps spare capacity. When it fills up, it allocates a bigger block and copies everything over. This is rare enough that appending to the end is amortized O(1).
Big O comparison
| Operation | Dynamic array | Linked list |
|---|---|---|
| Access by index | O(1) | O(n) |
| Search by value | O(n), O(log n) if sorted | O(n) |
| Append to end | amortized O(1) | O(1) with a tail pointer |
| Insert at start | O(n) — shifts everything | O(1) |
| Insert/delete in middle | O(n) — shifts the tail | O(1) if you hold the node, O(n) to find it |
| Extra memory per element | none beyond spare capacity | one or two pointers |
The key detail: the linked list's O(1) insert assumes you already have the node. If you first have to find it by index or value, the walk costs O(n), and the advantage disappears.
Cache locality: why arrays win in practice
The CPU loads memory in chunks called cache lines. When you read one array element, its neighbours come along for free, so the next reads are very fast. The hardware prefetcher also predicts sequential access well.
Linked list nodes are scattered, so each step may miss the cache and wait for main memory. That is why even operations with the same Big O, like iterating over all elements, often run noticeably faster on an array. For small and medium collections, shifting elements in an array is frequently cheaper than chasing pointers.
Both in code
Python and JavaScript arrays are dynamic arrays out of the box:
nums = [10, 20, 30]
nums.append(40) # amortized O(1)
nums.insert(0, 5) # O(n): every element shifts right
print(nums[2]) # O(1)
A minimal singly linked list in JavaScript:
class Node {
constructor(value, next = null) {
this.value = value;
this.next = next;
}
}
class LinkedList {
constructor() { this.head = null; }
prepend(value) { // O(1)
this.head = new Node(value, this.head);
}
insertAfter(node, value) { // O(1) when you hold the node
node.next = new Node(value, node.next);
}
*[Symbol.iterator]() {
for (let n = this.head; n; n = n.next) yield n.value;
}
}
const list = new LinkedList();
list.prepend(3);
list.prepend(1);
list.insertAfter(list.head, 2);
console.log([...list]); // [1, 2, 3]
When a linked list actually wins
- LRU cache. A hash map points to nodes of a doubly linked list. On every access, the node is moved to the front in O(1); the oldest node is dropped from the tail in O(1).
- Queues and deques with frequent adds at both ends. Python's
collections.dequeis built from linked blocks for this reason. Removing from the front of a plain array is O(n). - Splicing. Moving a whole chain of elements from one list to another is a few pointer changes, with no copying.
- Stable references. Nodes never move in memory, so pointers to them stay valid. Dynamic arrays relocate on growth.
- Free lists in memory allocators and kernels, where nodes are embedded in the objects themselves.
Common mistakes
- Picking a linked list "because insertion is O(1)" and then inserting by index, which is O(n) anyway.
- Using
array.shift()orlist.pop(0)as a queue on large data — use a deque or a queue structure instead. - Ignoring memory overhead: every node carries pointers and allocation metadata.
- Writing your own linked list when the standard library already has a suitable structure.