Lesson 22 of 30
Linked Lists: The Frontend-Relevant Parts
Why arrays beat linked lists for almost everything in JavaScript — and the real situations (undo history, an LRU cache) where the linked list's O(1) insertion actually earns its keep.
Linked lists get less airtime in this course than arrays, hashing, or trees — honestly, deliberately, because in JavaScript you will reach for a hand-rolled linked list far less often than the CS-curriculum reputation of the topic suggests. This lesson is about knowing the trade-off precisely enough to recognize the rare real situations where it matters.
The shape
function createNode(value) {
return { value, next: null };
}
const c = createNode(3);
const b = createNode(2);
const a = createNode(1);
a.next = b;
b.next = c;
// a -> b -> c -> nullNo array, no indices — just nodes, each holding a value and a reference to
the next node. To find the third element, you don't index into anything;
you walk a.next.next.value.
The one real advantage: O(1) insertion, if you already have the reference
function insertAtFront(head, value) {
const node = createNode(value);
node.next = head;
return node; // this is the new head
}This is genuinely O(1) — create a node, point it at the old head, done. Compare to an array:
function insertAtFrontArray(arr, value) {
arr.unshift(value); // O(n) — every existing element's index shifts up by one
return arr;
}unshift() is O(n) because array elements are stored by position — inserting
at the front means every other element's index has to change. A linked
list's nodes don't have positions, only pointers to each other, so inserting
anywhere given a reference to the right spot is O(1) regardless of list
size.
The real cost: everything else gets worse
That advantage comes with a genuine, unavoidable trade. Array indexing
(arr[500]) is O(1) — direct memory access by position. Reaching the 500th
node of a linked list means walking 500 .next references one at a time —
O(k) to reach the k-th node, with no shortcut. And you give up the
entire standard library — map, filter, reduce, sort, includes —
none of it exists for a hand-rolled linked list; you'd write every operation
yourself.
This is exactly why arrays are the default in essentially all JavaScript code, and linked lists are not: the O(1) insertion advantage is real, but it's rarely the actual bottleneck in typical frontend work, while losing O(1) index access and the entire array method ecosystem is a cost you'd pay on every single other operation.
Where it actually earns its keep: undo/redo
The situation where a linked list's shape is a genuinely better fit, not just a textbook example: an undo/redo history, where "current" sits somewhere in the middle of a timeline, and a new edit after undoing needs to discard everything ahead of "current" before continuing.
function makeHistoryNode(state) {
return { state, prev: null, next: null };
}
function recordNewState(current, newState) {
const node = makeHistoryNode(newState);
node.prev = current;
current.next = node; // discards any old "redo" chain that was here — it's simply unreferenced now
return node;
}Once current.next is overwritten, whatever redo chain existed there
before is simply gone — nothing referenced it anymore, and it becomes
eligible for garbage collection. That's an O(1) "discard everything
ahead and continue" operation. An array-based equivalent would need to
slice() off the discarded future states (O(n)) before pushing the new one
— not a disaster at typical undo-history sizes, but a real, measurable
difference, and the linked structure also just reads as the right model:
a chain with a current position, exactly matching the feature's own mental
model.
Notice prev in that example — this is a doubly linked list, where each
node points both forward and backward. That backward pointer is what lets
you move from "current" back toward earlier states without re-walking from
the beginning every time, and it's the exact structure the next-but-one
lesson's LRU cache is built on, for exactly the same reason: O(1) removal
of an arbitrary node, given a direct reference to it, requires being able to
reach its predecessor without a search.
What to remember
- A linked list's real advantage is O(1) insertion/removal given a reference to the relevant spot — an array's equivalent operations at the front or middle are O(n), since positions have to shift.
- That advantage comes at a real cost: O(k) to reach the k-th node (versus an array's O(1) index access), and none of the standard array methods exist for free.
- Arrays are the correct default for almost all JavaScript data; reach for a linked structure specifically when the problem's own shape is "a chain with a current position and cheap insertion/removal there" — undo/redo history is the clearest real example.
- A doubly linked list's extra
prevpointer is what enables O(1) removal of an arbitrary node given only a reference to it — the exact capability the upcoming LRU cache lesson depends on.
Check yourself
4 questions · pass 3/4 to unlock Stacks and Queues in Real UI Code
1.What is the time complexity of inserting a new node at the FRONT of a singly linked list, versus inserting a new element at the front of a JavaScript array with unshift()?
2.Despite that O(1) front-insertion advantage, why do most JavaScript codebases default to arrays rather than hand-rolled linked lists for everyday lists of data?
3.An undo/redo feature stores a linked list of past document states, with a 'current' pointer somewhere in the middle. Making a new edit after undoing several steps should discard the 'redo' states ahead of current and append the new state. Why does a linked list handle this more naturally than a plain array here?
4.What structural property distinguishes a doubly linked list from a singly linked list, and what capability does it add?
4 left to answer