Back to Catalog
Data Structures
Sequential Access
Linked Lists (Singly)
Nodes chained by pointers — O(1) insert/delete at head, O(n) everything else.
Intent & Description
Real-world Use Case
Hash table separate chaining — each bucket is a linked list of entries with the same hash. O(1) prepend per collision. Undo stack in a text editor — each edit is a node, undo pops the head. LRU cache — doubly linked list + hash map gives O(1) eviction.
Source
📌 TL;DR
Singly Linked List = O(1) at the head, O(n) everywhere else. Use for stacks, hash chaining, or when you have node pointers. Do not use when you need index access — that is what arrays are for.
Advantages
- O(1) prepend and head removal — no shifting, just pointer rewiring
- Truly dynamic — nodes allocated individually, no capacity planning
- Zero wasted capacity — each node allocated only when needed
- O(1) insert/delete at any position if you already hold the node pointer
Disadvantages
- O(n) access by index — must walk from head every time
- Terrible cache performance — nodes scattered across heap, no prefetch benefit
- Extra memory per node for the next pointer (8 bytes on 64-bit)
- No backwards traversal — singly-linked means forward-only
Implementation Example
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
class SinglyLinkedList {
#head = null;
#tail = null;
#size = 0;
get length() { return this.#size; }
// O(1) — Prepend to front (rewire head pointer)
prepend(value) {
const node = new Node(value);
node.next = this.#head;
this.#head = node;
if (!this.#tail) this.#tail = node;
this.#size++;
}
// O(1) — Append to end (requires tail pointer)
append(value) {
const node = new Node(value);
if (!this.#tail) {
this.#head = this.#tail = node;
} else {
this.#tail.next = node;
this.#tail = node;
}
this.#size++;
}
// O(1) — Remove head node
removeHead() {
if (!this.#head) return null;
const val = this.#head.value;
this.#head = this.#head.next;
if (!this.#head) this.#tail = null; // List is now empty
this.#size--;
return val;
}
// O(n) — Find by value (must walk from head)
find(value) {
let cur = this.#head;
while (cur) {
if (cur.value === value) return cur;
cur = cur.next;
}
return null;
}
// O(n) — Delete by value
delete(value) {
if (!this.#head) return false;
if (this.#head.value === value) { this.removeHead(); return true; }
let cur = this.#head;
while (cur.next) {
if (cur.next.value === value) {
if (cur.next === this.#tail) this.#tail = cur; // Update tail
cur.next = cur.next.next;
this.#size--;
return true;
}
cur = cur.next;
}
return false;
}
// O(n) — Reverse in-place (classic interview question)
reverse() {
let prev = null, cur = this.#head;
this.#tail = this.#head;
while (cur) {
const next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
this.#head = prev;
}
toArray() {
const result = [];
let cur = this.#head;
while (cur) { result.push(cur.value); cur = cur.next; }
return result;
}
}
const list = new SinglyLinkedList();
list.append(10);
list.append(20);
list.append(30);
list.prepend(5);
console.log(list.toArray()); // [5, 10, 20, 30]
list.reverse();
console.log(list.toArray()); // [30, 20, 10, 5]