Singly Linked List: Beginner to Expert 🚀
A complete guide to singly linked lists: theory, memory model, JavaScript implementation explained simply, complexity, interview patterns and expert tips.
Definition and Purpose
A Singly Linked List (SLL) is like a train of nodes, where each node carries a value and a link to the next node.
- Data — what the node carries
- Next pointer — a chain/link to the next node
Unlike arrays, linked list nodes don't sit next to each other in memory. They are scattered, but each one knows who comes after it.
Purpose
- Fast insertions and deletions anywhere in the list
- Dynamic memory — we don't need to know the size ahead of time
- Base structure for stacks, queues, LRU cache and more
Core Theory & Mechanics
- Head — first node of the list
- Tail — last node (next = null)
- Traversal — start at head, follow next pointers until the end
Memory Insight
Each node is an object in memory. The next pointer stores the address of the next node. This is not cache-friendly like arrays, because nodes are scattered across memory.
JavaScript Example — Explained Simply
Step 1: Node Class
class Node {
constructor(data) {
this.data = data; // store the value
this.next = null; // start with no next node
}
}Kid-friendly explanation
Imagine each node is a box. data is what's inside the box, next is an arrow pointing to the next box. Initially the box has no arrow (next = null) because it's alone.
Step 2: Print the Linked List
function printLinkedList(head) {
if (!head) {
console.log("Linked List not implemented");
return;
}
let pointer = head; // start at the first node
while (pointer != null) {
console.log(pointer.data); // show the box content
pointer = pointer.next; // follow the arrow to the next box
}
}- if (!head) — if the list is empty, show a message
- pointer = head — we start at the first box, but we keep head safe
- while (pointer != null) — keep going until the last box
- pointer = pointer.next — follow the arrow to the next box
Why we use a pointer
We don't want to lose the head. Head is like the front door of our linked list house. If we move head, we forget where the list starts.
Step 3: Create Head and Add Nodes
const head = new Node(23); // first box
let pointer = head; // start building from here
for (let k = 0; k <= 10; k++) {
pointer.next = new Node(k); // add a new box after current
pointer = pointer.next; // move pointer to the new box
}
printLinkedList(head);- const head = new Node(23) — first box, always remember where it is
- let pointer = head — temporary helper to add boxes without losing the head
- pointer.next = new Node(k) — create a new box and link it after the current one
- pointer = pointer.next — move pointer to the new box
- The loop runs 11 times (0 to 10), so we build boxes one by one
- printLinkedList(head) — follow arrows from the first box and print the numbers
head → [23] → [0] → [1] → [2] → ... → [10] → null
Key takeaways from this example
Always keep track of the head, otherwise you lose the list. Use a temporary pointer for adding nodes. Each loop iteration creates a new node and links it after the current node.
Time and Space Complexity
| Operation | Complexity | Notes |
|---|---|---|
| Access by index | O(n) | Must traverse from head |
| Search by value | O(n) | Linear search |
| Insert at head | O(1) | Constant time |
| Insert at tail | O(n) | O(1) if a tail pointer is maintained |
| Delete head | O(1) | Constant time |
| Delete tail | O(n) | Must traverse to the second-last node |
| Space | O(n) | Each node stores data plus a pointer |
Comparison Table
| Feature | Singly Linked List | Array |
|---|---|---|
| Memory | Dynamic + pointer overhead | Contiguous |
| Access by index | O(n) | O(1) |
| Insert / delete at head | O(1) | O(n) |
| Insert / delete at tail | O(n), or O(1) with tail pointer | O(1) amortized |
| Cache friendliness | Low | High |
Mental Models
- Train analogy — each node is a carriage, the arrow is the chain to the next carriage
- Boxes analogy — each node is a box with data inside and an arrow pointing to the next box
Common Mistakes
- Forgetting the head — you lose the whole list
- Forgetting the null check — the code crashes
- Off-by-one errors in loops — wrong node connections
Best Practices
- Keep head safe, use a separate pointer for traversal
- Consider a dummy node to simplify insert and delete logic
- Maintain a tail pointer for O(1) tail insertion
- Wrap the list in a class for a clean API
Anti-Patterns
- Using array-style tricks on a linked list
- Recursive traversal for long lists — risk of stack overflow
- Traversing the list multiple times when one pass would do
Real-World Relevance
- Frontend (React) — Virtual DOM diffing uses a similar linked structure
- Backend (Node.js) — middleware chains and task queues
- DSA & System Design — LRU caches, queues, adjacency lists
Advanced Topics
- Doubly Linked List
- Circular Linked List
- Skip List
- Linked List + HashMap for an LRU cache
- XOR Linked List
Interview Insights
Common coding questions
- Reverse a linked list
- Detect a cycle
- Merge two sorted linked lists
- Find the middle node
- Remove the Nth node from the end
Tricky follow-ups
- Recursive vs iterative approach
- Empty list and single-node edge cases
Expert Tips
- Draw the list while coding in interviews
- Always preserve the head
- Practice patterns like reversing sublists, rotations and cycle detection
- Understand memory, cache behaviour and pointers for real-world systems