latency.lab
Algorithms · Topic 06

Linked List

Level: foundationTool: runner pointers, and knowing when NOT to recurseTime: ~60 min

By the end you can

Linked lists are where "just use recursion, it's cleaner" stops being free advice. Almost every problem in this topic has a genuinely elegant recursive solution and a slightly less pretty iterative one — and the iterative one is usually the right answer, because a linked list's length isn't bounded the way a typical function's call stack is. This topic is as much about that judgment call as it is about the pointer manipulation itself.

Two runner techniques carry most of this topic

  • Fast/slow pointers ("the runner technique"). One pointer moves one step at a time, another moves two. This single idea gives you: the middle of a list in one pass (Middle of the Linked List), whether a cycle exists at all (Linked List Cycle), and — with one extra step — exactly where that cycle begins (Linked List Cycle II). All three are the same runner, applied to a slightly different question.
  • The gap technique. Advance one pointer n steps ahead of a second pointer, then move both together until the lead pointer runs out. Remove Nth Node From End of List is the clean version of this — it turns "I don't know the list's length" into a single pass instead of two.
The habit to build

Before coding, ask: "do I need to know the list's length, or a specific offset from the end, without a second pass?" If yes, that's the gap technique. "Do I need to find a midpoint or detect a loop without extra memory?" That's fast/slow. Recognizing which runner shape a problem wants is most of the work — the pointer bookkeeping is mechanical once you know the shape.

1 2 3 ▲ slow 4 5 ▲ fast ∅
Fast/slow runners on a 5-node list (plus the trailing null). After two steps, slow has moved one node at a time (now at 3) while fast has moved two at a time (now at 5) — when fast reaches the end, slow is sitting exactly at the middle.

Recursion depth is a real cost that Big-O hides

Reverse Linked List has a famously elegant three-line recursive solution. It's also, in the most literal sense, a ticking time bomb: it recurses once per node, so a list with hundreds of thousands of nodes recurses hundreds of thousands of levels deep — and blows the call stack. Big-O notation says both the recursive and iterative versions are O(n) time — it says nothing about the fact that one of them can crash a real process on a long enough input while the other never can. This is exactly the kind of thing "efficient code" means beyond just the complexity class: the iterative three-pointer walk (prev, curr, next) does the identical work with zero growing stack.

recursive-vs-iterative-reverse.cpp
// Elegant, and a real production risk on a long enough list --
// recurses once per node, so stack depth grows with n.
ListNode* reverse_recursive(ListNode* head) {
    if (!head || !head->next) return head;
    ListNode* new_head = reverse_recursive(head->next);
    head->next->next = head;
    head->next = nullptr;
    return new_head;
}

// Same O(n) time, same result -- but O(1) stack depth, always.
ListNode* reverse_iterative(ListNode* head) {
    ListNode* prev = nullptr;
    while (head) {
        ListNode* next = head->next;
        head->next = prev;
        prev = head;
        head = next;
    }
    return prev;
}

Reverse Nodes in k-Group has the same trap in a sneakier form: the "obvious" recursive solution recurses once per group, not once per node — which sounds safe, but for a small k on a long list, n / k is still large enough to overflow the stack. The fix is the same: do it iteratively, one flat loop over the whole list.

Common mistakes

  • Not using a dummy head node when the operation might need to change the head itself (Remove Nth Node From End, Merge k Sorted Lists) — without one, removing or replacing the actual first node becomes a separate special case instead of just falling out of the general logic.
  • Forgetting to restore state you mutated for a check (Palindrome Linked List reverses the second half to compare it — and needs to reverse it back before returning, or the list comes back corrupted even though the answer was correct).
  • Losing the "next" pointer before rewiring it — the single most common linked-list bug: writing curr->next = prev before saving the original curr->next in a temporary, which severs your only path to the rest of the list.
  • Confusing "same value" with "same node" in Copy List with Random Pointer — the whole point is that the copy's pointers must reference the *new* nodes, not accidentally alias the original list; a broken "copy" that just returns the same nodes will pass value checks but isn't a copy at all.

Takeaways

  • Fast/slow pointers solve "find the middle," "detect a cycle," and "find where a cycle starts" — the same runner idea underlies all three.
  • The gap technique (advance one pointer n steps first, then move both together) turns "an offset from the end" into a single pass instead of two.
  • A recursive solution being O(n) time doesn't mean it's safe — recursion depth is a real, separate cost that crashes on long lists regardless of what the time complexity says. Prefer the iterative form when one exists.
  • Use a dummy head whenever the head itself might need to change, and always save a node's next before you overwrite it.

Try it: the foundation, done the safe way

Reverse Linked List — get the iterative three-pointer walk automatic here, since the recursion-depth lesson above applies to several later problems in this topic too.

Live

Loading starter code…

Try it: the runner technique

Linked List Cycle is Floyd's tortoise and hare in its purest form — the same idea reappears (with one extra step) in Linked List Cycle II later in this set.

Live

Loading starter code…

Try it: the gap technique

Remove Nth Node From End of List turns "I don't know the length" into a single pass — and the dummy-head trick here is worth internalizing before Merge k Sorted Lists later in the set.

Live

Loading starter code…

Keep going

That's 3 of the 12 problems in this topic. Merge k Sorted Lists is worth doing once these three feel automatic — it's the one place in this topic complexity is graded on more than just correctness. See the full Linked List set →

Checkpoint · Linked List

5 questions · pass at 70%

Q01
Reverse Linked List has both a recursive and an iterative O(n) time solution. What's the actual difference between them that matters in practice?
WhyBoth are O(n) time, but the recursive version's stack depth scales with n, risking a stack overflow on long lists -- a real cost that time complexity alone doesn't show. The iterative version's stack usage stays constant regardless of list length.
Q02
What do Middle of the Linked List, Linked List Cycle, and Linked List Cycle II all have in common?
WhyAll three are variations on the same fast/slow pointer idea: advancing one pointer twice as fast as another. Recognizing that shared shape is the actual skill -- the exact bookkeeping differs slightly per problem.
Q03
Why does Remove Nth Node From End of List advance a pointer n steps ahead FIRST, rather than computing the list's length and then walking to the target position?
WhyBoth are O(n) time overall, but computing the length first requires one pass to count, then a second pass to reach the target -- two traversals. Advancing a lead pointer n steps ahead first lets both pointers finish the job together in a single pass.
Q04
Palindrome Linked List's O(1)-extra-space solution reverses the second half of the list to compare it against the first half. What must it do before returning, and why?
WhyReversing the second half is a real mutation of the list structure. Since the function's job is only to check if it's a palindrome (not to reverse it), leaving that reversal in place afterward would silently corrupt the list order for whatever code runs next -- the check's correctness doesn't excuse leaving mutated state behind.
Q05
Merge k Sorted Lists uses a min-heap of at most k elements at a time, rather than repeatedly scanning all k list-heads to find the minimum. Why does that change the complexity?
WhyFinding the minimum among k current candidates costs O(k) with a linear scan (repeated for every one of the N output nodes, giving O(N*k) total) versus O(log k) with a heap (giving O(N log k) total) -- a large gap once k grows, exactly the kind of hidden cost that shows up as a real timeout on large inputs.

Finished Linked List?

Pass the quiz to complete it automatically.