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 the needed node references, you can splice a node in or out in O(1) by rewiring pointers. In a singly , deletion also needs the previous node; a doubly linked node stores that reference itself. Neither case shifts the remaining elements.

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 linked list inside a least recently used (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.

Trade random access for pointer edits

Linked lists trade constant-time random access for constant-time splices with the needed node references; singly linked non-head deletion needs the previous node.

An array keeps elements side by side, while linked list nodes need not be contiguous.

Text version
  1. An array keeps elements side by side, while linked list nodes need not be contiguous.
  2. Following i links to element i makes indexed access O of n, rather than constant time.
  3. Unlinking a singly linked non-head node takes constant time when prev is available to update its link.
  4. Tail deletion needs a linear walk in a singly linked list, or constant time with doubly linked tail access.
  5. A singly linked node stores one pointer, while a doubly linked node stores two.
  6. A least recently used cache uses a doubly linked list for quick splices at known nodes.
Quick check

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?

Choose one answer

2. Operations and Complexity

OperationAverageWorstNotes
Access by indexO(n)O(n)Must traverse from head
Search by valueO(n)O(n)Linear walk
Insert/delete at headO(1)O(1)Rewire head pointer
Insert/delete at a known nodeO(1)O(1)Singly: need the previous node; doubly: O(1) directly
Insert at tailO(1) with tail pointerO(n) withoutKeep a tail pointer to append directly
Delete at tailO(1) for doubly linked list with tail pointerO(n) for singly linked listSingly linked deletion must find the previous node
Find middle / has cycleO(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. A dummy or sentinel head is a temporary node placed before the first real element. It lets deletion of the first real node use the same previous-node update as any other deletion.

A dummy node makes head deletion a normal splice

A dummy head supplies prev when deleting the first real node, so the same link update handles head deletion.

To delete node one from nodes one through five, place a dummy node before the head.

Text version
  1. To delete node one from nodes one through five, place a dummy node before the head.
  2. Prev starts at dummy, so the first real node has a previous node for deletion.
  3. Cur points to node one, whose value matches the target.
  4. Setting prev dot next to cur dot next bypasses node one without a special case.
  5. After breaking out of the loop, set the head to dummy dot next, which is node two.

3. Implementation

The example shows front insertion, deletion of the first matching value, reversal, and cycle detection. reverse changes links in place and returns the new head; assign that return value back to the list's head. has_cycle checks whether following next references can revisit a node.

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.

Save the next node before reversing a link

Reverse saves nxt, flips head.next, and advances both references; it returns the new head in linear time with constant extra space.

We reverse nodes one, two, and three, starting with prev set to None.

Text version
  1. We reverse nodes one, two, and three, starting with prev set to None.
  2. Save the next reference before changing node one's link.
  3. Flip the link so node one points to None.
  4. Advance prev to node one and head to node two.
  5. Save the next reference to preserve the remaining nodes.
  6. Flip the link so node two points to node one.
  7. Advance prev to node two and head to node three.
  8. Save the next reference as None because node three is the tail.
  9. Flip the final link so node three points to node two.
  10. Advance again, leaving prev at the reversed list and head at None.
  11. The loop ends because head is None.
  12. Return prev as the new head, then assign it back to the list.
Quick check

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?

Choose one answer

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.deque instead when you just need a fast double-ended — don't hand-roll one.
Quick check

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?

Choose one answer

5. Common Pitfalls

  • Losing the rest of the list: rewiring head.next before saving head.next orphans everything downstream. Always save nxt first.
  • 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.next or fast.next.next raises 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/fast start position determines whether you get the left-middle or right-middle on even length — confirm which the problem wants.

Fast and slow pointers meet inside a cycle

Slow advances one link and fast advances two; they meet inside the loop, while the fast and fast.next guard protects lists that end.

Both pointers start at node one, and node six links back to node three.

Text version
  1. Both pointers start at node one, and node six links back to node three.
  2. Slow follows one link to node two, while fast follows two to node three.
  3. Another round puts slow at node three and fast at node five.
  4. Fast hops through six to three, one behind slow, shrinking the gap by one each round.
  5. Both reach node five after moving, so matching references confirm a cycle.
  6. The guard below checks fast and fast dot next before each round, so lists that end are safe.
Quick check

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?

Choose one answer

6. Interview Cheat Sheet

  • " trades O(n) random access for O(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 in O(n)/O(1)."
  • "Deleting a node in a singly needs the previous node, so I track prev while traversing."

Follow-ups:

  • "Singly vs doubly linked?" — Doubly adds a prev pointer: O(1) delete given only the node and O(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 fast reaches the end, slow is 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.

Reading

On this page

My notes

Sign in to keep highlights and notes.

Glossary history

Click dotted jargon to save explanations here.