JavaScript linked lists and iterators
Build a singly linked list class that can be looped over with for...of.
On this page
JavaScript classes and generators.
- Build a singly linked list class that can be looped over with for...of.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
A linked list stores each value in a node that points to the next one. Adding at the front is O(1) and needs no shifting, but finding the n-th item means following n links. JavaScript arrays are usually faster in practice, but understanding lists helps with queues, undo chains, and many interview problems.
A class can make itself iterable by defining a generator method named [Symbol.iterator]. A generator function, written with an asterisk, produces values one at a time with yield. Then for...of, the spread operator, and Array.from all work on the list. The reverse method relinks the nodes in a single pass.
Code example
class Node {
constructor(value, next = null) {
this.value = value;
this.next = next;
}
}
class LinkedList {
constructor() {
this.head = null;
this.length = 0;
}
addFirst(value) {
this.head = new Node(value, this.head);
this.length++;
}
addLast(value) {
const node = new Node(value);
if (this.head === null) {
this.head = node;
} else {
let current = this.head;
while (current.next !== null) {
current = current.next;
}
current.next = node;
}
this.length++;
}
reverse() {
let previous = null;
let current = this.head;
while (current !== null) {
const next = current.next;
current.next = previous;
previous = current;
current = next;
}
this.head = previous;
}
*[Symbol.iterator]() {
for (let node = this.head; node !== null; node = node.next) {
yield node.value;
}
}
}
const list = new LinkedList();
list.addFirst(2);
list.addFirst(1);
list.addLast(3);
list.addLast(4);
console.log([...list].join(" -> "), "(" + list.length + " nodes)");
list.reverse();
console.log([...list].join(" -> "));
let sum = 0;
for (const value of list) {
sum += value;
}
console.log("sum " + sum);1 -> 2 -> 3 -> 4 (4 nodes) 4 -> 3 -> 2 -> 1 sum 10
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
class Node { constructor(value, next = null) { this.value = value; this.next = next; }} class LinkedList { constructor() { this.head = null; this.length = 0; } addFirst(value) { this.head = new Node(value, this.head); this.length++; } addLast(value) { const node = new Node(value); if (this.head === null) { this.head = node; } else { let current = this.head; while (current.next !== null) { current = current.next; } current.next = node; } this.length++; } reverse() { let previous = null; let current = this.head; while (current !== null) { const next = current.next; current.next = previous; previous = current; current = next; } this.head = previous; } *[Symbol.iterator]() { for (let node = this.head; node !== null; node = node.next) { yield node.value; } }} const list = new LinkedList();list.addFirst(2);list.addFirst(1);list.addLast(3);list.addLast(4);console.log([...list].join(" -> "), "(" + list.length + " nodes)");list.reverse();console.log([...list].join(" -> "));let sum = 0;for (const value of list) { sum += value;}console.log("sum " + sum);Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| The new node points to the old head and becomes the head: O(1). | — | — | |
| addLast walks to the end: O(n). | — | — | |
| A generator method that makes the list iterable. | — | — | |
| Spread uses the iterator to turn the list into an array. | — | 1 -> 2 -> 3 -> 4 (4 nodes) | |
| Reversal points each node backwards. | — | 1 -> 2 -> 3 -> 4 (4 nodes) | |
| The same values in the opposite order. | — | 1 -> 2 -> 3 -> 4 (4 nodes) 4 -> 3 -> 2 -> 1 | |
| for...of also works with the iterator. | — | 1 -> 2 -> 3 -> 4 (4 nodes) 4 -> 3 -> 2 -> 1 sum 10 |
Behind the scenes
How JavaScript processes this code
A JavaScript engine parses and runs the source. A function body runs when the function is called. console.log writes to the console.
Cost and common mistakes
addFirst O(1), addLast O(n), reverse O(n), iteration O(n).
- Save current.next before changing it in reverse, or the rest of the list is lost.
- Keeping a tail reference makes addLast O(1) but requires care when the list becomes empty.
- Arrays have better cache behaviour and are the default choice. Use a list when you need O(1) insertion in the middle given a node.
Try it yourself
How would you find the middle node in one pass?
Reveal answer and reasoning
Walk with two pointers: a slow one moving one node at a time and a fast one moving two. When the fast one reaches the end, the slow one is at the middle.