Linked Lists
5 min read
Data Structures & Algorithms Index
Tier 1 -- Foundations
Tier 2 -- Patterns
Tier 3 -- Dynamic Programming
Data Structures & Algorithms Index
Tier 1 -- Foundations
Tier 2 -- Patterns
Tier 3 -- Dynamic Programming
Linked Lists
1. What It Is and Why It Exists
A stores each element in its own node, and each node holds a pointer to the next node (singly linked) or to both neighbors (doubly linked). Nodes are not contiguous in memory, so there is no address arithmetic — to reach element i you must walk i pointers, making access O(n). In exchange you get the one thing arrays are bad at: given a reference to a node, you can splice a node in or out in O(1) by rewiring , with no shifting of the rest of the structure.
That trade-off is the entire reason linked lists exist and the entire reason interviewers use them. They are the backing structure when you need cheap insert/delete at arbitrary positions you already hold a handle to: the doubly inside an LRU cache, the chains in a , implementations. The "why it works" intuition: a linked list buys O(1) structural edits by giving up O(1) random access — the opposite of an array. Interview problems are mostly pointer manipulation (reverse, detect cycle, find middle, merge) where the skill is not losing a reference and handling the head/tail/empty edges.
You're building an LRU cache that needs to evict the least-recently-used entry and promote recently accessed entries to the front — both operations happening thousands of times per second. You already maintain a hash map for O(1) lookups by key. Which data structure should back the ordering of cache entries, and why?
2. Operations and Complexity
| Operation | Average | Worst | Notes |
|---|---|---|---|
| Access by index | O(n) | O(n) | Must traverse from head |
| Search by value | O(n) | O(n) | Linear walk |
| Insert/delete at head | O(1) | O(1) | Rewire head pointer |
| Insert/delete at a known node | O(1) | O(1) | Singly: need the previous node; doubly: O(1) directly |
| Insert/delete at tail | O(1) with tail ptr | O(n) without | Keep a tail pointer to make it O(1) |
| Find middle / has cycle | O(n) | O(n) | Fast/slow pointers, O(1) space |
Space is O(n) plus a pointer per node (more overhead than an array). The operations interviewers probe: deleting a node in a singly requires the previous node (or the copy-next trick), and O(1) tail insertion only holds if you maintain a tail pointer. The dummy/sentinel head node is the expected technique for clean edge handling.
3. Implementation
Singly with the interview-critical operations and the dummy-head pattern:
class ListNode: def __init__(self, val=0, nxt=None): self.val = val self.next = nxt class LinkedList: def __init__(self): self.head = None def push_front(self, val): # O(1): new node points to old head self.head = ListNode(val, self.head) def delete_value(self, target): # O(n) to find, O(1) to unlink dummy = ListNode(0, self.head) # sentinel: removes the "delete head" special case prev, cur = dummy, self.head while cur: if cur.val == target: prev.next = cur.next # O(1) splice-out; needs prev (singly linked) break prev, cur = cur, cur.next self.head = dummy.next # The two canonical interview routines: def reverse(head): # O(n) time, O(1) space prev = None while head: nxt = head.next # save next BEFORE rewiring (or you lose the list) head.next = prev # flip the pointer prev, head = head, nxt return prev # new head def has_cycle(head): # Floyd: O(n) time, O(1) space slow = fast = head while fast and fast.next: slow = slow.next # +1 fast = fast.next.next # +2; if there's a loop they meet if slow is fast: return True return False
In a real interview you implement the node class yourself — there is no idiomatic stdlib singly , and that is the point of these problems. When you actually need a doubly linked, O(1)-both-ends sequence (e.g. a deque), reach for collections.deque.
You are implementing a singly linked list's delete_value method. A colleague suggests using a dummy (sentinel) head node at the start of the operation. What problem does this pattern specifically solve?
4. When to Reach for It
- The problem hands you a (
ListNode) — reverse, merge, reorder, detect/remove cycle, find middle, partition. - You need
O(1)insert/delete at positions you already hold a pointer to and do not need random access (e.g., the recency list inside an LRU cache → doubly + hash map). - You need
O(1)splicing of sublists without shifting elements. - Reach for
collections.dequeinstead when you just need a fast double-ended — don't hand-roll one.
You are building an LRU cache for a web server. The cache needs to evict the least-recently-used entry on every cache miss and promote any accessed entry to 'most recent' in O(1) time. Which data structure combination is most appropriate for the recency-ordering component?
5. Common Pitfalls
- Losing the rest of the list: rewiring
head.nextbefore savinghead.nextorphans everything downstream. Always savenxtfirst. - Null/empty edges: empty list (
head is None), single node, operating on the head — use a dummy/sentinel head to kill the special cases. - Fast-pointer cycle bug: loop condition must be
while fast and fast.nextorfast.next.nextraises on even-length lists. - Singly-linked delete without
prev: you cannot unlink a node knowing only itself unless you copy the next node's value over it (and that fails for the tail). - Forgetting to update the tail pointer after inserting/deleting at the end → later
O(1)tail inserts corrupt the list. - Off-by-one finding the middle:
slow/faststart position determines whether you get the left-middle or right-middle on even length — confirm which the problem wants.
You are implementing a linked list traversal that reverses a singly-linked list in-place. A teammate writes the following logic:
curr.next = prev
prev = curr
curr = nxt
But they forgot to save nxt before the first line. What is the consequence of this omission?6. Interview Cheat Sheet
- " trades
O(n)random access forO(1)insert/delete at a known node — the inverse of an array." - "I use a dummy/sentinel head so deleting the head isn't a special case."
- "Reverse is iterative pointer flipping in
O(n)time,O(1)space; cycle detection is Floyd's fast/slow inO(n)/O(1)." - "Deleting a node in a singly needs the previous node, so I track
prevwhile traversing."
Follow-ups:
- "Singly vs doubly linked?" — Doubly adds a
prevpointer:O(1)delete given only the node andO(1)backward traversal, at the cost of an extra pointer per node. LRU cache needs doubly linked. - "Find the middle in one pass?" — Fast/slow pointers: when
fastreaches the end,slowis at the middle.O(n)time,O(1)space. - "Reverse in groups of k?" — Reverse k nodes at a time, reconnect the group's tail to the next reversed group; use a dummy head to anchor the result.
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.