Linked List
A sequence of nodes where each node stores a value and a pointer to the next, giving O(1) insertion/deletion at a known position but O(n) access by index.
Definition
A linked list stores elements in separate nodes scattered through memory; each node holds a value and a reference (next) to the following node. The list is identified by its head pointer, and the last node points to null. Variants add a prev pointer (Doubly Linked List) or connect the tail back to the head (Circular Linked List).
Its trade-off is the mirror image of an Array: no random access (reaching index i requires walking i pointers), but once you hold a pointer to a node, splicing a node in or out next to it is O(1) with no shifting. That property is what makes it the backbone of LRU Cache, hash-table Separate Chaining, and adjacency lists.
In interviews the linked list matters less as a container and more as a pointer-manipulation exercise: reversal, cycle detection with Fast & Slow Pointers, merging sorted lists, and finding the middle or the k-th node from the end without knowing the length.
Intuition
A mental model before the formal terms.
Think of a scavenger hunt. Each clue tells you where the next clue is; you cannot jump to clue 7 without reading clues 1 through 6. But inserting a new clue is trivial: write the new clue pointing to the old "next" location, then edit the previous clue to point to the new one. Nobody else has to move.
An array is a bookshelf where every book has a fixed slot; a linked list is a set of books each hiding a note saying where the next book is. Reading in order costs the same; jumping to the middle does not.
How it works
- Node:
{ value, next }. List:head(and oftentailandsize). pushFront(v):node.next = head; head = node.O(1).pushBack(v): with atailpointer,tail.next = node; tail = nodeinO(1); without one, walk to the end inO(n).insertAfter(node, v):new.next = node.next; node.next = new. Two pointer writes,O(1).deleteAfter(node):node.next = node.next.next.O(1)— deleting a node requires its predecessor, which is why singly linked deletion by node reference isO(n)unless you copy the next node's value into it.get(i)/search(v): start athead, follownextuntil indexior the value is found.O(n).- A dummy (sentinel) head node whose
nextis the real head removes special cases for inserting or deleting at position 0.
Why it works
Because order is encoded in pointers rather than addresses, changing the sequence only requires rewriting the pointers adjacent to the change — a constant number of writes. Arrays must physically move elements to preserve the address formula.
The absence of an address formula is exactly why access is linear: the only way to find node i is to follow i links.
Reversal works by iterating with three pointers (prev, cur, next): each step flips one link, and the invariant "everything before cur is already reversed and prev is its head" holds until cur is null.
Floyd's cycle detection works because if a cycle of length L exists, a pointer moving 2 steps gains one step per iteration on a pointer moving 1 step, so they meet within L iterations after both enter the cycle.
Operations
| Operation | Description | Cost |
|---|---|---|
| pushFront(v) | New node becomes head. | O(1) |
| pushBack(v) | Append at tail; O(1) with a tail pointer, O(n) without. | O(1) |
| popFront() | Advance head; return old value. | O(1) |
| insertAfter(node, v) | Rewire two pointers around an existing node. | O(1) |
| deleteAfter(node) | Skip the next node by pointer rewrite. | O(1) |
| get(i) | Walk i links from the head. | O(n) |
| search(v) | Linear scan following next. | O(n) |
| reverse() | Flip every next pointer in one pass with three pointers. | O(n) |
Recognition
How to tell a problem wants this.
- The input is explicitly given as
ListNode/headand the task is to reverse, merge, reorder, remove or detect a cycle. - "In
O(1)extra space" for a sequence problem — you must rewire pointers instead of copying to an array. - You need
O(1)insert/delete at an arbitrary known position, or anO(1)move-to-front (caches, MRU lists). - Unknown or unbounded stream length where you never need random access.
Interactive demo
Play, step, change the input. ← → and space work too.
Showing the closely related Singly Linked List visualization.
1append(v): walk to the tail; tail.next = Node(v)2prepend(v): node = Node(v); node.next = head; head = node3insert(v, i): walk to node i-1; node.next = prev.next; prev.next = node4delete(v): walk until curr.value == v; prev.next = curr.next5reverse(): prev = null; curr = head6 while curr: next = curr.next; curr.next = prev; prev = curr; curr = next7 head = prevPseudocode
1class Node: value, next = null2class LinkedList: head = null, tail = null, size = 03pushFront(v): n = Node(v); n.next = head; head = n; if tail == null: tail = n; size++4pushBack(v): n = Node(v); if tail: tail.next = n else head = n; tail = n; size++5insertAfter(node, v): n = Node(v); n.next = node.next; node.next = n; if node == tail: tail = n; size++6deleteAfter(node): gone = node.next; node.next = gone.next; if gone == tail: tail = node; size--7search(v): cur = head; while cur: if cur.value == v return cur; cur = cur.next; return null8reverse(): prev = null; cur = head; while cur: nxt = cur.next; cur.next = prev; prev = cur; cur = nxt; head = prevImplementation
1from typing import Generic, Optional, TypeVar2 3T = TypeVar("T")4 5 61 · Node and list state7class ListNode(Generic[T]):8 __slots__ = ("value", "next")9 10 def __init__(self, value: T, next: "Optional[ListNode[T]]" = None) -> None:11 self.value = value12 self.next = next13 14 15class LinkedList(Generic[T]):16 def __init__(self) -> None:17 self.head: Optional[ListNode[T]] = None18 self.tail: Optional[ListNode[T]] = None19 self._n = 020 21 def __len__(self) -> int:22 return self._n23 242 · Push at either end25 def push_front(self, v: T) -> None:26 node = ListNode(v, self.head)27 self.head = node28 if self.tail is None:29 self.tail = node30 self._n += 131 32 def push_back(self, v: T) -> None:33 node = ListNode(v)34 if self.tail is not None:35 self.tail.next = node36 else:37 self.head = node38 self.tail = node39 self._n += 140 413 · Insert / delete after a known node42 def insert_after(self, node: ListNode[T], v: T) -> None:43 fresh = ListNode(v, node.next)44 node.next = fresh45 if node is self.tail:46 self.tail = fresh47 self._n += 148 49 def delete_after(self, node: ListNode[T]) -> T:50 gone = node.next51 if gone is None:52 raise ValueError("nothing after node")53 node.next = gone.next54 if gone is self.tail:55 self.tail = node56 self._n -= 157 return gone.value58 594 · Search by value60 def search(self, v: T) -> Optional[ListNode[T]]:61 cur = self.head62 while cur is not None:63 if cur.value == v:64 return cur65 cur = cur.next66 return None67 685 · Reverse in place69 def reverse(self) -> None:70 prev: Optional[ListNode[T]] = None71 cur = self.head72 self.tail = self.head73 while cur is not None:74 nxt = cur.next75 cur.next = prev76 prev = cur77 cur = nxt78 self.head = prev__slots__onListNoderemoves the per-instance__dict__, shrinking each node and speeding attribute access.push_frontbuilds the node withself.headas itsnext;push_backappends throughtail.insert_after/delete_aftersplice around a node the caller holds, usingisto compare withtail(identity, not equality).searchcompares with==, so values that define__eq__match structurally.reverseperforms the classic three-variable walk; Python tuple assignment could compress it to one line.
Every node is a full Python object (~56 bytes with slots); a list of the same values is far smaller and faster to traverse.
- Python has no linked list in the standard library;
collections.dequeis a doubly linked list of blocks and covers most needs. - Use
is None/is not Nonefor null checks — a node whose value is falsy would breakif node. - Reference counting frees nodes as soon as they are unlinked; a cycle needs the garbage collector.
- Using
while cur:where a node class defines__len__or__bool__— it may be falsy. - Dropping the rest of the list by writing
cur.next = prevbefore savingcur.next. - Recursing over a long list — Python's default recursion limit is 1000.
- Memory: C++ needs explicit
delete(or smart pointers) for nodes; JS and Python free unreachable nodes automatically. - Null:
nullptrin C++,nullin JS/TS (withstrictNullChecksenforcing guards),Nonein Python (compare withis). - Standard library: C++ ships
std::forward_list/std::list; JS and Python have no linked list — Python'sdequeis the closest. - Recursion on lists: Python's 1000-frame limit and JS's ~10k frames make iterative traversal mandatory for long lists; C++ stacks are larger but still finite.
Complexity
| Operation | Average | Worst | Note |
|---|---|---|---|
| Access | O(n) | O(n) | O(1) for head; tail too with a tail pointer. |
| Search | O(n) | O(n) | |
| Insert | O(1) | O(1) | Given a pointer to the neighbour; O(n) to find a position by index. |
| Delete | O(1) | O(1) | Given the predecessor; O(n) otherwise in a singly linked list. |
| Update | O(n) | O(n) | O(1) once the node is located. |
| Push front / Pop front | O(1) | O(1) | |
| Push back | O(1) | O(1) | Requires a tail pointer. |
| Reverse | O(n) | O(n) | |
| Space | O(n) | One pointer (8 bytes) of overhead per node; two for doubly linked. | |
Advantages & disadvantages
O(1)insertion and deletion at any position for which you already hold a node pointer.- No resizing or shifting; grows one node at a time, so no wasted capacity and no
O(n)copy spikes. - Nodes can be spliced between lists in
O(1)— the basis of LRU Cache and deque implementations. - Works naturally as a stack or queue (with a tail pointer).
O(n)access by index andO(n)search — no Binary Search.- Extra memory per node for the pointer(s), often doubling the footprint for small values.
- Poor cache locality: nodes are scattered across the heap, so a full traversal is several times slower than an array scan of equal length.
- Singly linked lists cannot walk backwards; deleting a node by reference needs its predecessor.
Use cases
- Bucket chains in Hash Table collision handling (Separate Chaining).
- Doubly linked list + hash map for LRU Cache / LFU Cache with
O(1)move-to-front. - Queues and deques (Queue, Deque) with
O(1)operations at both ends. - Adjacency lists in graph implementations; free lists in memory allocators.
- Undo/redo histories, playlists, and any sequence with frequent middle insertions.
- Frequent insertions/deletions at known positions (you hold the node), especially at the front.
- You need to splice nodes between structures in
O(1)— caches, schedulers, free lists. - Interview problems that hand you a
ListNodeand forbid extra space.
- You need random access or binary search — use an Array / Dynamic Array.
- Memory or cache performance matters and the elements are small — pointer overhead and scattered nodes dominate.
- You only ever append and iterate — a Dynamic Array is simpler and faster.
Alternatives
Common mistakes
- Losing the rest of the list by overwriting
cur.nextbefore saving it in a temporary (nxt = cur.next). - Forgetting to update
tail(orhead) after inserting/deleting at the ends, leaving a stale pointer. - Null-pointer errors on empty or single-node lists; fix with a dummy head and explicit checks.
- Deleting a node by reference in a singly linked list without its predecessor — impossible except by copying the next node's value.
- Off-by-one in fast/slow pointer loops (
fast && fast.nextvsfast.next && fast.next.next) when finding the middle. - Accidentally creating a cycle when reordering nodes and never terminating the list with
null.
Interview patterns
- Iterative and recursive reversal, including reverse in groups of k and reverse between positions.
- Fast/slow pointers to find the middle, detect a cycle, and locate the cycle start.
- Two-pointer gap of
kto remove the k-th node from the end in one pass. - Merge two sorted lists with a dummy head; merge k lists with a Min-Heap.
- Dummy head sentinel to unify insert/delete at position 0 with the general case.
- Splitting and re-merging lists (reorder list, odd-even list, sort list with merge sort).
- Recognizing the approach from an array and a targetIntermediate
- Array versus linked listBeginner
- When space complexity mattersIntermediate
- Questions to ask before binary searchingIntermediate
- Two SumBeginner