Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
What is Linked List?
Linked List is a linear data structure where elements (called nodes) are connected via pointers rather than being stored in contiguous memory. Think of it like a scavenger hunt: each clue (node) tells you where to find the next clue, but you cannot jump directly to clue number 7 without following clues 1 through 6. This gives linked lists O(1) insertions and deletions at a known position (just rewire the pointers) but O(n) access to an arbitrary element (you must walk the chain). Unlike arrays where you pay for shifting elements on insert or delete, a linked list only requires updating a constant number of pointers. The trade-off is that you lose random access and pay extra memory per element for storing the pointer itself.Pattern Recognition Cheatsheet
Universal Templates
These three templates cover roughly 90% of linked list interview problems. Memorize them cold; you should be able to write each one in under 60 seconds with no thinking.Pattern Recognition Checklist
Use Linked List Patterns When
- Detecting cycles (Floyd’s algorithm)
- Finding middle element
- Reversing all or part of list
- Merging sorted lists
- Checking palindrome structure
- Removing duplicates
Common Techniques
- Dummy head for edge cases
- Fast/slow pointers for cycle/middle
- Prev/curr/next for reversal
- Two-pointer for nth from end
- In-place to save space
When to Use
Fast and Slow Pointers
Reversal
Merging
Dummy Node
Pattern Variations
1. Fast and Slow Pointers (Floyd’s)
Imagine two runners on a circular track — one runs at twice the speed of the other. No matter where they start, the fast runner will eventually lap the slow runner and they will meet. This is the core insight behind Floyd’s algorithm: if a cycle exists, the two pointers must collide; if no cycle exists, the fast pointer reaches the end first. For finding the middle node, the same idea applies: when the fast pointer reaches the end (having moved 2 steps per iteration), the slow pointer (1 step per iteration) is exactly at the midpoint. Complexity: O(n) time, O(1) space for both operations.2. Reverse Linked List
Reversing a linked list is like reversing a chain of paper clips: at each step you detach the current clip from what comes after it and attach it to the growing reversed chain behind you. You need exactly three variables to avoid losing track:prev (the already-reversed portion), current (the node you are processing), and next_node (a temporary save so you do not lose the rest of the list when you rewire current.next).
Complexity: O(n) time, O(1) space for in-place reversal.
Edge cases to watch: empty list (return None), single-node list (already reversed), and partial reversal where left == right (no-op).
3. Merge Two Sorted Lists
This is the “zipper” pattern: given two sorted chains, interleave them into one sorted chain by always picking the smaller head. A dummy node at the start eliminates special-case logic for choosing the initial head. Complexity: O(n + m) time where n and m are the lengths of the two lists. O(1) extra space because we re-use existing nodes. Interview tip: This same merge logic is the foundation for Merge Sort on linked lists and for the “Merge K Sorted Lists” problem (use a min-heap of heads or pairwise merge).4. Remove Nth Node From End
The “gap” technique: advance the fast pointer n+1 steps ahead of slow. Then move both at the same speed. When fast hits None, slow is exactly one node before the target — perfectly positioned to bypass it. The dummy node handles the edge case where the node to remove is the head itself. Complexity: O(n) time with a single pass, O(1) space. Edge case: If n equals the length of the list, you are removing the head. Without the dummy node this requires a special check.Worked LeetCode Problems
These five problems are the foundation of every linked list interview. If you can solve all five from memory in under 15 minutes total, you are ready for the medium tier.LC 206 — Reverse Linked List (Easy)
Brute force: push every value into an array, walk it backwards, build a new list. O(n) time, O(n) space, and you have not demonstrated any pointer skill. Optimized: in-place reversal using the three-pointer template. O(n) time, O(1) space.LC 141 — Linked List Cycle (Easy)
Brute force: walk the list and put each visited node reference in a HashSet. If you re-encounter a node, there is a cycle. O(n) time, O(n) space. Optimized: Floyd’s tortoise and hare. Two pointers, fast moves 2x. If they meet, there is a cycle. O(n) time, O(1) space.LC 21 — Merge Two Sorted Lists (Easy)
Brute force: collect all values, sort, build a new list. O((n+m) log(n+m)) time, O(n+m) space. Wastes the existing sorted property entirely. Optimized: zipper merge with a dummy node. O(n+m) time, O(1) space (reusing existing nodes).LC 19 — Remove Nth Node From End (Medium)
Brute force: two passes. First pass counts the length L. Second pass walks to position L - n and unlinks. O(n) time, O(1) space, but two traversals. Optimized: one-pass with a fast/slow gap of N+1. O(n) time, O(1) space, single pass.LC 23 — Merge K Sorted Lists (Hard)
Brute force 1 — collect and sort: gather all N nodes into an array, sort, rebuild. O(N log N) time, O(N) space. Brute force 2 — sequential pairwise merge: merge list 1 with list 2, result with list 3, etc. O(k * N) time — the early lists are re-traversed k times. Optimized A — min-heap: push the head of each list onto a min-heap. Pop the smallest, append, push that node’snext. O(N log k) time, O(k) space.
Classic Problems
| Problem | Pattern | Key Insight |
|---|---|---|
| Cycle Detection | Fast/Slow | Floyd’s algorithm |
| Middle of List | Fast/Slow | Fast moves 2x speed |
| Reverse List | Iterative | Track prev, curr, next |
| Merge K Lists | Divide/Heap | Merge pairs or use min-heap |
| Intersection | Length Diff | Align starts then traverse |
| Palindrome | Reverse Half | Reverse second half and compare |
Interview Problems by Company
- Easy
- Medium
- Hard
| Problem | Company | Key Concept |
|---|---|---|
| Reverse List | All FAANG | Basic reversal |
| Merge Two Lists | All FAANG | Two pointers |
| Delete Node | Apple, Amazon | No prev access trick |
| Middle of List | Amazon | Fast/slow |
Interview Tips
Always Use Dummy Node
Always Use Dummy Node
Reversal Template -- Memorize This
Reversal Template -- Memorize This
curr.next before overwriting it. Once you set curr.next = prev, the original forward link is gone forever.Floyd's Cycle Detection -- Finding the Cycle Start
Floyd's Cycle Detection -- Finding the Cycle Start
Drawing Pointer Diagrams
Drawing Pointer Diagrams
next pointers. When you modify a pointer, erase the old arrow and draw the new one. This prevents the most common class of linked list bugs: losing a reference to a node because you overwrote a pointer before saving it.Ask yourself at every step: “Have I saved every reference I will need later?”Practice Problems
Reverse Linked List
Linked List Cycle II
Merge K Sorted Lists
LRU Cache
Practice Roadmap
Day 2: Fast/Slow Pointers
- Solve: Middle of List, Cycle Detection, Cycle II
- Focus: Floyd’s algorithm
Day 3: In-Place Modifications
- Solve: Reorder List, Palindrome Linked List
- Focus: Combining techniques
Curated LeetCode List
A focused grind list grouped by difficulty. Solve all of these and you can confidently field any linked list question in a 45-minute interview.| # | Problem | Difficulty | Pattern | Why It Matters |
|---|---|---|---|---|
| 206 | Reverse Linked List | Easy | In-place reverse | The single most-asked linked list problem. Iterative AND recursive. |
| 141 | Linked List Cycle | Easy | Fast/slow | Floyd’s algorithm in its simplest form. |
| 21 | Merge Two Sorted Lists | Easy | Dummy + zipper | Foundation for merge sort and merge K lists. |
| 876 | Middle of the Linked List | Easy | Fast/slow | Convention: returns the second middle for even length. |
| 83 | Remove Duplicates from Sorted List | Easy | Single pass | Tests basic prev/curr manipulation without a dummy. |
| 234 | Palindrome Linked List | Easy | Compose middle + reverse + walk | Classic three-technique composition. |
| 160 | Intersection of Two Linked Lists | Easy | Two-pointer redirect | Elegant length-equalization trick. |
| 142 | Linked List Cycle II | Medium | Floyd’s phase 2 | Find the cycle start. The math is the interview lesson. |
| 19 | Remove Nth Node From End | Medium | Fast/slow gap | Canonical N+1 gap technique. |
| 92 | Reverse Linked List II | Medium | Sublist reverse | Reverse from L to R in one pass. |
| 86 | Partition List | Medium | Two dummies | Classic “split into two then stitch” pattern. |
| 2 | Add Two Numbers | Medium | Dummy + carry | Tests off-by-one and carry propagation. |
| 24 | Swap Nodes in Pairs | Medium | Dummy + lookahead | Recursive solution is cleaner than iterative — mention both. |
| 138 | Copy List with Random Pointer | Medium | HashMap or interleave-and-split | Two valid solutions with different space trade-offs. |
| 143 | Reorder List | Medium | Middle + reverse + zipper | Composes three patterns into one. |
| 148 | Sort List | Medium | Merge sort on lists | O(n log n) time, O(log n) recursion. |
| 23 | Merge K Sorted Lists | Hard | Heap or D&C | The K-way merge problem. |
| 25 | Reverse Nodes in K-Group | Hard | Reverse subroutine + outer loop | Composes reversal inside a counting loop. |
| 146 | LRU Cache | Medium (treated Hard) | Doubly linked + HashMap | The most-asked design question at FAANG. |
| 460 | LFU Cache | Hard | Two HashMaps + DLL of DLLs | The harder cousin of LRU. |
Interview Q&A — Senior-Level Walkthroughs
Q: Reverse a linked list -- both iterative and recursive. What are the trade-offs?
Q: Reverse a linked list -- both iterative and recursive. What are the trade-offs?
- State the technique: “Iterative uses three pointers (prev, curr, next) and rewires links one at a time. Recursive walks to the tail, then unwinds, flipping each pointer on the way back.”
- Write the iterative version first — it is the canonical answer.
- Write the recursive version next.
- State complexities: iterative is O(n) time, O(1) space. Recursive is O(n) time, O(n) space due to the call stack.
- Mention edge cases: empty list, single node.
1 to 2 to 3:- Iteration 1: prev=None, curr=1 — save nxt=2, set 1.next=None, advance prev=1, curr=2.
- Iteration 2: prev=1, curr=2 — save nxt=3, set 2.next=1, advance prev=2, curr=3.
- Iteration 3: prev=2, curr=3 — save nxt=None, set 3.next=2, advance prev=3, curr=None.
- Loop exits. Return prev=3. List is now
3 to 2 to 1.
group_prev pointer to know where to attach the reversed segment.prev and next pointers of every node and then return the old tail as the new head. There is no need for a separate prev variable in the loop because each node already stores its predecessor. The complexity is still O(n) time, O(1) space, but the code is shorter.- “I would convert the list to an array, reverse the array, and rebuild the list.” This is O(n) space and misses the entire point. It signals you have not internalized in-place pointer manipulation.
- Writing the iterative version but forgetting to save
curr.nextbefore overwriting it. The list is silently truncated and you return a one-node list. - Recursive version that does not set
head.next = None— this leaves a cycle between the original head and its successor, producing infinite loops on traversal.
- LC 92 (Reverse Linked List II) — reverse only between positions L and R.
- LC 25 (Reverse Nodes in K-Group) — the natural extension.
- LC 234 (Palindrome Linked List) — uses reversal as a subroutine.
Q: Detect a cycle in a linked list and find where the cycle begins (LC 142).
Q: Detect a cycle in a linked list and find where the cycle begins (LC 142).
- Phase 1: detect the cycle using fast/slow pointers. If they meet, a cycle exists.
- Phase 2: reset one pointer to head. Move both at speed 1. They meet at the cycle start.
- Justify phase 2 with the path-length argument: distance head to start equals distance meeting-point to start (mod cycle length).
- State complexity: O(n) time, O(1) space. Beats the HashSet approach on space.
- Let
a= distance from head to cycle start. - Let
b= distance from cycle start to where slow and fast first meet. - Let
c= remaining distance from meeting point back around to cycle start. - When they meet, slow has traveled
a + b, fast has traveleda + b + n*(b + c)for some integer n (n times around the cycle). - Since fast moves 2x slow:
2(a + b) = a + b + n(b + c), which simplifies toa = n(b + c) - b = (n-1)(b+c) + c. - This means: walking
asteps from head equals walkingcsteps plus some whole loops from the meeting point. So if we reset one pointer to head and walk both at speed 1, they meet exactly at the cycle start.
a = c (mod cycle) identity hold. With 3:1, you would need different reset logic, and there is no clean closed form.- Storing visited nodes in a HashSet. Works but uses O(n) space — the interviewer asked for O(1).
- Hashing by value rather than identity. Two different nodes can have the same value but be distinct objects. The HashSet must key on object reference, not value.
- Not handling the empty list or single-node-without-cycle edge case, which causes a null dereference in the first iteration.
- LC 141 (Linked List Cycle) — the simpler version: just detect, do not locate.
- LC 287 (Find the Duplicate Number) — Floyd’s applied to an array via index-as-pointer.
- LC 202 (Happy Number) — Floyd’s applied to a function-iteration sequence.
Q: Merge K sorted linked lists (LC 23). Compare heap vs divide-and-conquer.
Q: Merge K sorted linked lists (LC 23). Compare heap vs divide-and-conquer.
- State the brute force: sequential pairwise merge, O(k * N) time. Acknowledge it works but is suboptimal.
- Present approach A: min-heap of K heads. Pop, append, push that node’s next. O(N log k) time, O(k) space.
- Present approach B: divide-and-conquer pairwise merge. log k rounds, each doing O(N) work. O(N log k) time, O(log k) recursion stack.
- Compare trade-offs: heap is easier to code and adapts to streams; D&C has better cache locality for static input.
- Sequential pairwise: round 1 merges 250+250=500 nodes. Round 2 merges 500+250=750. Round 3 merges 750+250=1000. Total: 2250 comparisons.
- Heap: each node is pushed and popped once at O(log 4) = 2 cost. Total: 1000 * 2 = 2000 operations.
- Divide-and-conquer: round 1 merges 4 lists into 2 (each merge is 500 comparisons, 2 merges = 1000). Round 2 merges 2 into 1 (1000 comparisons). Total: 2000 comparisons.
__lt__. The index is a unique tiebreaker that ensures every tuple is comparable.- “Just merge them one by one.” This is O(k * N) — repeatedly merging into the growing accumulator means early lists are re-traversed each time.
- Using a max-heap and reversing at the end. Wastes time and signals confusion about heap direction.
- Forgetting to push
node.nextafter popping. The result is incomplete — only the heads ever come out.
- LC 21 (Merge Two Sorted Lists) — the building block.
- LC 378 (Kth Smallest Element in a Sorted Matrix) — heap K-way merge applied to rows.
- LC 632 (Smallest Range Covering Elements from K Lists) — a variation using the same heap structure.
Q: Design an LRU Cache with O(1) get and put (LC 146).
Q: Design an LRU Cache with O(1) get and put (LC 146).
- State the requirement: get and put both in O(1), eviction of least recently used on overflow.
- Identify why a single data structure is not enough: HashMap has no order, linked list has no O(1) lookup.
- Combine them: HashMap maps key to node reference; doubly linked list maintains usage order.
- Use sentinel head and tail nodes to eliminate edge cases.
- Walk through get and put step by step.
get a node, you need to remove it from its current position and move it to the head. Removing from the middle of a singly linked list requires finding the previous node, which is O(n). A doubly linked list lets you splice it out in O(1) using node.prev and node.next.Why sentinel head and tail? Without sentinels, every insert and remove has special cases for “is this the head or tail of the list?” Sentinels guarantee every real node has both a prev and a next, so the splice operations are uniform.min_freq tracker. On access, move the node from its current frequency list to the next-higher frequency list. On eviction, pull from the head of the min_freq list. This is LC 460.collections.OrderedDict which already does this. Could you implement LRU in 5 lines?” Answer: yes — OrderedDict.move_to_end(key) on access and popitem(last=False) for eviction give the LRU semantics directly. Java’s LinkedHashMap with accessOrder=true does the same. Mention this in interviews to demonstrate you know the standard library, but always be ready to implement from scratch since the interviewer is testing the underlying mechanics.- Using a singly linked list: O(n) per operation because you cannot splice in the middle in O(1).
- Storing values directly in the HashMap with a separate list of keys: this works but you need to find a key in the list (O(n)) to remove it, defeating the design.
- Forgetting to remove the evicted key from the HashMap: leads to a memory leak that grows unboundedly.
- LC 460 (LFU Cache) — the harder cousin.
- LC 432 (All O(1) Data Structure) — design a data structure with O(1) increment, decrement, getMaxKey, getMinKey.
- “Design Data-Intensive Applications” by Martin Kleppmann, chapter on caching.
Interview Questions
1. Why does Floyd's cycle detection algorithm guarantee that the fast and slow pointers will meet inside a cycle, and why can't fast 'skip over' slow?
1. Why does Floyd's cycle detection algorithm guarantee that the fast and slow pointers will meet inside a cycle, and why can't fast 'skip over' slow?
- Once both pointers are inside the cycle, think about the relative distance between them. Each iteration, fast gains exactly 1 step on slow (fast moves 2, slow moves 1, net gain = 1). So the gap shrinks by 1 every step — it is impossible for fast to “jump over” slow because the gap decreases linearly from whatever it is down to 0.
- More formally: if the cycle length is C and the gap between fast and slow when slow enters the cycle is
d, they will meet after exactlyC - dmore iterations. This is O(n) becaused < CandC <= n. - This is also why the algorithm works to find the cycle start in phase 2. Let the distance from head to cycle start be
a, and the meeting point beksteps past the cycle start. The math showsa = C - k, so resetting one pointer to head and walking both at speed 1 makes them converge at the cycle entrance. - Example: In a list
1 -> 2 -> 3 -> 4 -> 5 -> 3(cycle back to node 3), slow enters the cycle at node 3 while fast is already at node 5. Gap is 1 within the cycle (length 3). After 2 more steps (C - gap = 3 - 1), they meet at node 5.
- If the fast pointer moved 3 steps per iteration instead of 2, would the algorithm still work? Under what conditions might it fail?
- How would you modify Floyd’s algorithm to return the length of the cycle after detecting it?
2. Explain the dummy node (sentinel) technique. When is it essential versus just convenient?
2. Explain the dummy node (sentinel) technique. When is it essential versus just convenient?
if head is None or if head == target checks scattered everywhere. Interviewers want to see whether you proactively eliminate bug surface area.Strong Answer:- A dummy node is a fake node placed before the real head so that every real node, including the head, has a predecessor. This means operations like “delete a node” or “insert before a node” never need a special case for the head.
- It is essential when the head itself might be removed (e.g., “remove all nodes with value X” where X is the head value), when merging two lists (either could provide the first element), or when reversing a sublist that starts at position 1.
- It is convenient but not strictly necessary when you are only reading or traversing, like finding the middle node — no pointers are being rewritten, so the head cannot change.
- The cost is one extra allocation. In an interview setting, that is negligible. In a hot loop processing millions of lists in production (e.g., a custom allocator for network packet chains), you might avoid it and handle edge cases explicitly to save allocation overhead.
- Example: In
remove_nth_from_end, ifnequals the list length, you are removing the head. Without a dummy node, you needif fast is None after n steps, return head.next. With a dummy node, the same loop logic handles it automatically — zero special cases.
- In a language without garbage collection (C/C++), what extra responsibility does the dummy node create, and how would you handle it?
- Can you think of a linked list problem where using a dummy node actually makes the solution harder or more confusing?
3. Reverse a linked list iteratively. Now walk me through exactly what happens if you forget to save `current.next` before overwriting it.
3. Reverse a linked list iteratively. Now walk me through exactly what happens if you forget to save `current.next` before overwriting it.
- The iterative reversal uses three variables:
prev,curr, andnext_node. Each iteration: (1) savecurr.nextintonext_node, (2) setcurr.next = prev, (3) advanceprev = curr, (4) advancecurr = next_node. - If you skip step 1 and go straight to
curr.next = prev, you have just severed the link to the rest of the list.curr.nextnow points backwards, and you have no reference to the node that was next. The remaining tail of the list is leaked — unreachable and lost. - Concretely: for
1 -> 2 -> 3, if at node 2 you do2.next = 1without saving node 3 first, you now have1 <- 2and node 3 is floating in memory with nothing pointing to it. Your loop variablecurrcannot advance because you have no saved reference to 3. - This is the single most common linked list bug in interviews. Drawing the pointer diagram before coding makes it almost impossible to make this mistake because you visually see that you are about to orphan a node.
- How would you reverse a linked list recursively, and what is the space complexity difference compared to the iterative approach?
- How do you reverse nodes in groups of K? Walk me through how the reversal subroutine plugs into the outer loop.
4. You need to detect whether a singly linked list is a palindrome in O(n) time and O(1) space. Describe your approach.
4. You need to detect whether a singly linked list is a palindrome in O(n) time and O(1) space. Describe your approach.
- Step 1: Use fast/slow pointers to find the middle of the list. For even-length lists, slow lands on the second middle node, which is exactly where the second half begins.
- Step 2: Reverse the second half of the list in place starting from
slow. - Step 3: Compare nodes from the head and from the reversed second-half head. If all values match, it is a palindrome.
- Step 4 (production-grade, often missed): Reverse the second half again to restore the original list structure. In an interview, mentioning this earns bonus points because it shows you think about callers who still hold references to the list.
- Example: For
1 -> 2 -> 3 -> 2 -> 1: middle is node 3, reverse second half to get1 -> 2 -> 3 <- 2 <- 1. Compare: 1==1, 2==2, done — palindrome confirmed. - Time: O(n) for finding middle + O(n/2) reversal + O(n/2) comparison = O(n). Space: O(1), only pointer variables.
- What changes if the list is doubly linked? Does the problem become trivial?
- If the interviewer says “you must not modify the list at all,” what is the best you can do for space complexity?
5. Compare and contrast using a HashMap versus Floyd's algorithm for cycle detection. When would you pick one over the other?
5. Compare and contrast using a HashMap versus Floyd's algorithm for cycle detection. When would you pick one over the other?
- HashMap approach: Store every visited node’s reference (not value) in a set. If you encounter a node already in the set, there is a cycle. Time O(n), space O(n). Dead simple to implement and debug.
- Floyd’s approach: Two pointers, fast and slow. Time O(n), space O(1). More subtle to implement correctly but uses constant extra memory.
- Choose HashMap when: you are debugging in production and want the simplest, most readable detection; memory is not constrained; you also want to record which nodes are in the cycle.
- Choose Floyd’s when: the list is enormous (millions of nodes) and you cannot afford O(n) extra memory; you are in an interview and the interviewer explicitly says “O(1) space”; or you also need to find the cycle start (Floyd’s phase 2 gives this for free).
- Real-world example: In a garbage collector’s mark phase, cycle detection in object graphs uses techniques similar to Floyd’s because allocating a HashMap proportional to the heap size would be self-defeating.
- Subtle gotcha with HashMap: you must hash by node identity (memory address), not by node value. Two nodes can have the same value but be different nodes. In Python, the default
setuses object identity for unhashable objects, but in Java you would use anIdentityHashSetor store references directly.
- Can you detect a cycle in a linked list using no extra space at all and without modifying the list? (Hint: think about what constraints make this theoretically impossible.)
- If the list has no cycle but is 10 billion nodes long, which approach would you pick and why?
6. How would you merge K sorted linked lists efficiently? Walk me through the trade-offs between different approaches.
6. How would you merge K sorted linked lists efficiently? Walk me through the trade-offs between different approaches.
- Brute force — merge one by one: Merge list 1 and list 2, then merge the result with list 3, and so on. Time: O(k*N) where N is total nodes, because early lists get re-traversed with each merge. This is the naive extension of “merge two sorted lists.”
- Min-heap (priority queue): Push the head of each list onto a min-heap. Pop the smallest, append it to the result, and push that node’s
next(if it exists). Time: O(N log k) because each of the N nodes is pushed/popped once, and heap operations are O(log k). Space: O(k) for the heap. - Divide and conquer (pairwise merge): Pair up the K lists and merge each pair. Repeat until one list remains. This is merge sort’s merge phase applied to lists-of-lists. Time: O(N log k) — same as the heap. Space: O(log k) for recursion stack if done recursively, or O(1) extra if done iteratively.
- When to pick which: The heap approach is easier to code in an interview and handles dynamic streams well (new lists can be added). Divide-and-conquer has better cache locality and lower constant factors for static inputs. In practice, if K is small (say under 20), the brute-force sequential merge is fine and simplest to maintain.
- Example: Merging 4 lists of 250 nodes each (1000 total). Heap: 1000 * log(4) = 2000 operations. Pairwise: 2 rounds of merging — round 1 merges 4 lists into 2 (500 comparisons each), round 2 merges 2 into 1 (1000 comparisons) = 2000 total. Sequential: round 1 = 500, round 2 = 750, round 3 = 1000 = 2250. Difference grows with K.
- If the K lists arrive as a stream (you do not know K upfront), which approach adapts best?
- How would you implement the min-heap comparison in a language where list nodes are not natively comparable (e.g., Python’s
heapqwith custom objects)?
7. Design an LRU Cache using a linked list. Why does this problem require a *doubly* linked list and not a singly linked one?
7. Design an LRU Cache using a linked list. Why does this problem require a *doubly* linked list and not a singly linked one?
- An LRU Cache needs two operations in O(1):
get(key)andput(key, value). On every access, the touched entry must move to the front (most recently used). On capacity overflow, the entry at the back (least recently used) must be evicted. - The HashMap gives O(1) lookup by key. The doubly linked list maintains the usage order — head is most recent, tail is least recent.
- Why doubly linked? When you access a node, you need to remove it from its current position and move it to the head. Removing a node from the middle of a singly linked list requires knowing the previous node, which means O(n) traversal to find it. A doubly linked list stores
prevandnext, so removal is O(1) — just rewirenode.prev.next = node.nextandnode.next.prev = node.prev. - The HashMap stores
key -> node_reference, so you jump directly to the node without traversal. Combined with O(1) doubly-linked-list removal and insertion-at-head, bothgetandputare O(1). - Production detail: Python’s
collections.OrderedDictis literally a HashMap + doubly linked list under the hood. Java’sLinkedHashMapwithaccessOrder=truedoes the same. In interviews, knowing this shows you understand that standard libraries already implement this pattern.
- How would you make this LRU Cache thread-safe? What are the concurrency pitfalls?
- What changes if the requirement shifts from LRU to LFU (Least Frequently Used)?
8. You are given a linked list where each node also has a `random` pointer that can point to any node in the list or null. Deep copy this list.
8. You are given a linked list where each node also has a `random` pointer that can point to any node in the list or null. Deep copy this list.
- Approach 1 — HashMap (O(n) space): First pass: iterate the original list, create a copy of each node, and store
original -> copyin a HashMap. Second pass: iterate again and set each copy’snextandrandomby looking up the corresponding originals in the map. Time O(n), space O(n). - Approach 2 — Interleaving (O(1) space): This is the clever trick. Step 1: For each original node A, create copy A’ and insert it right after A, so the list becomes
A -> A' -> B -> B' -> C -> C'. Step 2: Set random pointers —A'.random = A.random.next(because A.random’s copy is always the next node after A.random). Step 3: Separate the interleaved list back into two lists. Time O(n), space O(1). - The interleaving approach is more complex to code and easier to get wrong, but it demonstrates a deeper understanding of pointer manipulation. In an interview, I would mention both approaches, implement the HashMap one first (correct and clean), and then explain the interleaving optimization if time permits.
- Key insight that most candidates miss: In the HashMap approach, you must be careful that you are mapping node references, not node values. Two nodes can have the same value but are different objects with different random pointers.
- If the list could contain cycles via the
randompointers (not just thenextpointers), does your HashMap approach still work? What about the interleaving approach? - How would you verify that your deep copy is correct? What test cases would you write?
9. What is the time complexity of inserting an element at the beginning, middle, and end of a singly linked list versus an array? When does a linked list actually win in practice?
9. What is the time complexity of inserting an element at the beginning, middle, and end of a singly linked list versus an array? When does a linked list actually win in practice?
- Insert at beginning: Linked list O(1) — create a node, point it to old head, done. Array O(n) — shift every element right. Clear win for linked lists.
- Insert at middle (known position): Linked list O(1) for the insertion itself if you already have a pointer to the position. But finding that position is O(n). Array O(n) for shifting. In practice, roughly equivalent unless you maintain pointers to strategic positions.
- Insert at end: Linked list O(1) if you maintain a tail pointer, O(n) otherwise. Array O(1) amortized (dynamic array with doubling). Roughly equal.
- When linked lists actually win in practice: When you are doing frequent insertions and deletions in the middle of a known position (e.g., an LRU cache where you hold a direct reference to the node). When the elements are large (moving large structs in an array is expensive). When you cannot tolerate worst-case O(n) pauses from array resizing.
- When arrays win despite worse theoretical complexity: Almost everywhere else. CPU caches fetch contiguous memory in cache lines (typically 64 bytes). Arrays exploit this — sequential access in an array is 10-100x faster than chasing pointers scattered across the heap. Bjarne Stroustrup (creator of C++) famously demonstrated that
std::vectorbeatsstd::listeven for insert-heavy workloads due to cache effects. - Real-world example: The Linux kernel uses intrusive linked lists extensively (via
list_head) because they need O(1) removal of known elements from scheduler queues, and the elements are already heap-allocated for other reasons.
- What is an “intrusive” linked list, and why does the Linux kernel prefer it over a standard linked list?
- If you are building a text editor’s buffer, would you use an array, a linked list, or something else entirely? Why?
10. Walk me through how you would debug a linked list problem where your solution produces a cycle in the output that should not have one.
10. Walk me through how you would debug a linked list problem where your solution produces a cycle in the output that should not have one.
- Step 1 — Reproduce minimally: Reduce the input to the smallest list that still triggers the bug. Usually 3-4 nodes is enough for linked list issues. Draw the list and all pointers on paper.
- Step 2 — Trace pointer assignments: Go through the code line by line on your minimal example. At each step, draw the state of every node’s
nextpointer. The bug is almost always one of these: (a) you forgot to terminate a pointer withnullafter an operation, (b) you assignednode.nextto a node that already appears earlier in the list, (c) you reused a variable that still points to a node from a previous iteration. - Step 3 — Check the usual suspects:
- After reversing a sublist, did you set the original head’s
nextto null or to the correct successor? Forgetting this is the number-one cause of accidental cycles after partial reversals. - After merging, did the last node of one list still point to its old next instead of null?
- When reordering (like the “reorder list” problem), the node that becomes the new tail must have its
nextset to null.
- After reversing a sublist, did you set the original head’s
- Step 4 — Add detection: Temporarily add a Floyd’s cycle check after your operation. If a cycle is detected, print the meeting point node’s value — this tells you which node’s
nextpointer is wrong. - Example: In “Reorder List” (
1->2->3->4->5becomes1->5->2->4->3), a common bug is not setting node 3’snextto null after splitting and reversing the second half. Node 3 still points to node 4, but node 4 now points to node 3 (from the reversal), creating a3 -> 4 -> 3cycle.
- In a language without garbage collection, how would an accidental cycle cause a memory leak, and how would you detect it?
- You are reviewing a teammate’s linked list code in a PR. What are the top 3 things you check for before approving?
Interview Deep-Dive
You are given a singly linked list that may or may not be a palindrome. The constraint: O(n) time and O(1) space. Walk me through your full approach.
You are given a singly linked list that may or may not be a palindrome. The constraint: O(n) time and O(1) space. Walk me through your full approach.
- The approach has three phases. Phase 1: use the fast/slow pointer technique to find the middle of the list. When
fastreaches the end,slowis at the midpoint. Phase 2: reverse the second half of the list starting fromslow. Phase 3: compare the first half and the reversed second half node by node. If all values match, the list is a palindrome. - Critical detail: for even-length lists like
1 -> 2 -> 2 -> 1, the slow pointer lands on the second2(the start of the second half). For odd-length lists like1 -> 2 -> 3 -> 2 -> 1, the slow pointer lands on3(the center node), and we can skip it during comparison because a single center element does not affect palindrome status. - After the comparison, a production-quality implementation should reverse the second half back to restore the original list structure. Interviewers notice whether you mention this — it signals that you think about side effects and API contracts, not just solving the puzzle.
- The space is O(1) because we reverse in-place and compare with two pointers. No array copy, no stack, no recursion.
Design an LRU Cache with O(1) get and put operations. What data structures do you combine and why?
Design an LRU Cache with O(1) get and put operations. What data structures do you combine and why?
- The core insight is that neither a HashMap nor a linked list alone is sufficient. A HashMap gives O(1) lookup by key but has no ordering. A doubly linked list maintains insertion/access order and supports O(1) removal (given a pointer to the node), but has O(n) lookup. Combining them gives both: the HashMap maps keys to list nodes, and the linked list maintains recency order.
- On
get(key): look up the node in the HashMap in O(1). If found, move it to the front of the linked list (O(1) with a doubly linked list — detach the node by updating its neighbors’ pointers, then insert at the head). Return the value. - On
put(key, value): if the key already exists, update the value and move to front. If the key is new, create a node, insert at front, add to HashMap. If the cache exceeds capacity, evict the tail node (the least recently used), remove it from both the list and the HashMap. - Implementation detail that catches people: use a dummy head and dummy tail sentinel to avoid null-pointer edge cases when the list is empty or has one element. Without sentinels, insert and remove have multiple special cases.
- This exact structure powers Memcached’s internal slab allocator, Redis’s approximate LRU, and most in-memory caches.
get also modifies the list (moves to front), so it is actually a write. A common workaround is to batch recency updates: reads are lock-free and append to a buffer, a background thread periodically processes the buffer and updates the list under a write lock. This is what Caffeine (a Java caching library) does. (3) Lock-free approaches using CAS on the linked list pointers, though these are complex and rarely worth implementing from scratch.Merge K sorted linked lists. Compare the heap approach versus the divide-and-conquer approach in terms of time, space, and real-world trade-offs.
Merge K sorted linked lists. Compare the heap approach versus the divide-and-conquer approach in terms of time, space, and real-world trade-offs.
- Heap approach: Maintain a min-heap of size K containing one node from each list (the current head). Extract the minimum from the heap in O(log K), append it to the result, and push the next node from that list into the heap. Total time: O(N log K) where N is the total number of nodes across all lists. Space: O(K) for the heap plus O(1) for the output (reusing existing nodes).
- Divide-and-conquer approach: Pair up the K lists and merge each pair using the standard two-list merge. This halves the number of lists. Repeat until one list remains. There are log K levels of merging, and at each level the total work across all pairs is O(N). Total time: O(N log K). Space: O(log K) for the recursion stack, O(1) extra for the merges (in-place pointer manipulation).
- Both are O(N log K) time, but the constants differ. The heap approach has overhead from heap operations (sift-up, sift-down, comparison functions, potential cache misses from pointer chasing in the heap). The divide-and-conquer approach performs simple sequential pointer operations that are more cache-friendly.
- In practice, divide-and-conquer tends to be faster for linked lists because the merge step is purely sequential pointer manipulation with excellent locality. The heap approach is simpler to code and better when you need a streaming solution (processing elements one at a time as they arrive).
- Edge case both must handle: empty lists in the input. The heap approach naturally handles this (just do not push null nodes). The divide-and-conquer approach handles it via the base case of the two-list merge.
Given two linked lists that may intersect at some node, find the intersection point. You cannot modify the lists, and the solution must be O(n) time, O(1) space.
Given two linked lists that may intersect at some node, find the intersection point. You cannot modify the lists, and the solution must be O(n) time, O(1) space.
- The elegant approach: start two pointers, one at each list head. Advance both one step at a time. When a pointer reaches the end of its list, redirect it to the head of the other list. If the lists intersect, the two pointers will meet at the intersection node. If they do not intersect, both pointers will reach null simultaneously.
- Why it works: let list A have length
a(before intersection) +c(shared tail), and list B have lengthb+c. Pointer A travelsa + c + bsteps before reaching the intersection on its second pass. Pointer B travelsb + c + asteps. Sincea + c + b = b + c + a, they arrive at the intersection at the same time. - If there is no intersection, pointer A travels
a + c + b + cand pointer B travelsb + c + a + c. Wait — actually for the no-intersection case, pointer A travelsa + btotal and pointer B travelsb + atotal, both reaching null at the same moment. So the null check naturally handles the no-intersection case. - Alternative approach: compute the lengths of both lists. Advance the longer list’s pointer by the length difference. Then walk both in lockstep until they meet. Same O(n) time, O(1) space, but requires two passes.
next pointer. Once two lists share a node, that node’s next is a single pointer to the same subsequent node. They can never split apart. The shared portion is always a suffix, forming a Y-shape. This is a fundamental structural property of singly linked lists and is why the “intersection” problem is well-defined. In a doubly linked list, the same constraint applies — each node has one next and one prev, so the structure cannot diverge once merged. Only in a graph (where nodes can have multiple outgoing edges) could two paths merge and then split.