Linked List Pattern Explained
Master the essential linked list techniques for coding interviews: in-place reversal, cycle detection with fast/slow pointers, and the dummy head technique.
What is a Linked List?
A linked list is a linear data structure where elements (nodes) are connected through pointers rather than stored in contiguous memory. Each node contains data and a reference to the next node. Unlike arrays, linked lists excel at insertions and deletions but sacrifice random access.
Singly vs Doubly Linked Lists
Singly Linked List: Each node has a next pointer only. You can only traverse forward. Most interview problems use singly linked lists.
// Singly Linked List Node
class ListNode {
val: number
next: ListNode | null
constructor(val: number = 0, next: ListNode | null = null) {
this.val = val
this.next = next
}
}
// Example: 1 -> 2 -> 3 -> null
const head = new ListNode(1, new ListNode(2, new ListNode(3)))Doubly Linked List: Each node has both next and prev pointers. You can traverse in both directions, which simplifies certain operations like deletion.
// Doubly Linked List Node
class DoublyListNode {
val: number
next: DoublyListNode | null
prev: DoublyListNode | null
constructor(val: number = 0) {
this.val = val
this.next = null
this.prev = null
}
}
// Used in: LRU Cache, browser history, text editorsWhen to Use Linked List Techniques
Consider linked list patterns when:
- In-place reversal needed - Reverse a portion or the entire list without extra space
- Cycle detection - Determine if a list has a loop using fast/slow pointers
- Merging lists - Combine sorted lists efficiently
- Finding middle element - Use fast/slow pointers to find the midpoint in one pass
- Removing nth element from end - Use two pointers with fixed distance
- Reordering nodes - Interleave, partition, or rearrange without creating new nodes
Pattern Variations
1. Two Pointers: Fast and Slow
The fast/slow pointer technique (also called Floyd's algorithm or tortoise and hare) uses two pointers moving at different speeds. The slow pointer moves one step at a time, while the fast pointer moves two steps. This pattern is essential for:
- Finding the middle of a linked list
- Detecting cycles
- Finding the start of a cycle
function findMiddle(head: ListNode | null): ListNode | null {
if (!head) return null
let slow: ListNode | null = head
let fast: ListNode | null = head
// Fast moves 2x speed, so when fast reaches end,
// slow is at the middle
while (fast !== null && fast.next !== null) {
slow = slow!.next
fast = fast.next.next
}
return slow
}
// Example: 1 -> 2 -> 3 -> 4 -> 5
// When fast reaches 5, slow is at 3 (middle)
// For even length: 1 -> 2 -> 3 -> 4
// Returns second middle (3)2. Dummy Head Technique
Using a dummy node (sentinel) before the actual head simplifies edge cases. When the head might change (deletions, insertions at front), the dummy node eliminates special-case handling.
function removeElements(head: ListNode | null, val: number): ListNode | null {
// Dummy node handles edge case where head needs removal
const dummy = new ListNode(0, head)
let current = dummy
while (current.next !== null) {
if (current.next.val === val) {
// Skip the node with matching value
current.next = current.next.next
} else {
current = current.next
}
}
// Return actual head (skip dummy)
return dummy.next
}
// Without dummy: Would need special logic for removing head
// With dummy: Same logic handles all positions3. In-Place Reversal
Reversing a linked list (or a portion of it) is a fundamental technique. The key insight is tracking three pointers: previous, current, and next.
function reverseList(head: ListNode | null): ListNode | null {
let prev: ListNode | null = null
let current = head
while (current !== null) {
// Save next before we overwrite current.next
const next = current.next
// Reverse the link
current.next = prev
// Move prev and current one step forward
prev = current
current = next
}
// prev is now the new head
return prev
}
// Visual: 1 -> 2 -> 3 -> null
// Step 1: null <- 1 2 -> 3 -> null
// Step 2: null <- 1 <- 2 3 -> null
// Step 3: null <- 1 <- 2 <- 3
// Result: 3 -> 2 -> 1 -> nullCommon Problems & Solutions
Problem 1: Detect Cycle in Linked List
Question: Given the head of a linked list, determine if it has a cycle. A cycle exists if a node can be reached again by following the next pointers.
function hasCycle(head: ListNode | null): boolean {
if (!head || !head.next) return false
let slow: ListNode | null = head
let fast: ListNode | null = head
while (fast !== null && fast.next !== null) {
slow = slow!.next
fast = fast.next.next
// If they meet, there's a cycle
if (slow === fast) {
return true
}
}
// Fast reached the end - no cycle
return false
}
// Why it works:
// If there's a cycle, fast will eventually "lap" slow
// They'll meet inside the cycle
// If no cycle, fast reaches nullFollow-up: Find where the cycle begins.
function detectCycle(head: ListNode | null): ListNode | null {
if (!head || !head.next) return null
let slow: ListNode | null = head
let fast: ListNode | null = head
// Phase 1: Detect if cycle exists
while (fast !== null && fast.next !== null) {
slow = slow!.next
fast = fast.next.next
if (slow === fast) {
// Phase 2: Find cycle start
// Reset slow to head, keep fast at meeting point
slow = head
while (slow !== fast) {
slow = slow!.next
fast = fast!.next
}
return slow // Cycle start
}
}
return null // No cycle
}
// Math insight: Distance from head to cycle start equals
// distance from meeting point to cycle start (going around)Problem 2: Merge Two Sorted Lists
Question: Merge two sorted linked lists into one sorted list.
function mergeTwoLists(
list1: ListNode | null,
list2: ListNode | null
): ListNode | null {
// Dummy head simplifies building the result
const dummy = new ListNode(0)
let tail = dummy
while (list1 !== null && list2 !== null) {
if (list1.val <= list2.val) {
tail.next = list1
list1 = list1.next
} else {
tail.next = list2
list2 = list2.next
}
tail = tail.next
}
// Attach remaining nodes (one list may be longer)
tail.next = list1 !== null ? list1 : list2
return dummy.next
}
// Example:
// list1: 1 -> 3 -> 5
// list2: 2 -> 4 -> 6
// Result: 1 -> 2 -> 3 -> 4 -> 5 -> 6Problem 3: Remove Nth Node From End
Question: Given a linked list, remove the nth node from the end and return the head. Do it in one pass.
function removeNthFromEnd(
head: ListNode | null,
n: number
): ListNode | null {
const dummy = new ListNode(0, head)
let first: ListNode | null = dummy
let second: ListNode | null = dummy
// Move first pointer n+1 steps ahead
// This creates a gap of n nodes between first and second
for (let i = 0; i <= n; i++) {
first = first!.next
}
// Move both pointers until first reaches end
while (first !== null) {
first = first.next
second = second!.next
}
// second.next is the node to remove
second!.next = second!.next!.next
return dummy.next
}
// Example: 1 -> 2 -> 3 -> 4 -> 5, n = 2
// Gap of 2: when first is at end, second is at 3
// Remove 4 (2nd from end): 1 -> 2 -> 3 -> 5Problem 4: Reverse Linked List II (Partial Reversal)
Question: Reverse the nodes between positions left and right (1-indexed).
function reverseBetween(
head: ListNode | null,
left: number,
right: number
): ListNode | null {
if (!head || left === right) return head
const dummy = new ListNode(0, head)
let prev = dummy
// Move prev to node before 'left' position
for (let i = 1; i < left; i++) {
prev = prev.next!
}
// Start reversing from the 'left' node
let current = prev.next
// Reverse nodes between left and right
for (let i = 0; i < right - left; i++) {
// Take the next node and move it to just after prev
const nextNode = current!.next
current!.next = nextNode!.next
nextNode!.next = prev.next
prev.next = nextNode
}
return dummy.next
}
// Example: 1 -> 2 -> 3 -> 4 -> 5, left=2, right=4
// Step 1: 1 -> 3 -> 2 -> 4 -> 5
// Step 2: 1 -> 4 -> 3 -> 2 -> 5
// Result: 1 -> 4 -> 3 -> 2 -> 5Time & Space Complexity
| Operation | Time | Space |
|---|---|---|
| Traverse/Search | O(n) | O(1) |
| Insert at head/tail | O(1) | O(1) |
| Insert at position | O(n) | O(1) |
| Delete (given node) | O(1) | O(1) |
| Reverse | O(n) | O(1) |
| Cycle detection | O(n) | O(1) |
| Merge two sorted | O(n + m) | O(1) |
Pro Tips
- Always use dummy head: When the head might change, a dummy node eliminates edge cases. Return
dummy.nextat the end. - Draw the pointers: Linked list manipulation is visual. Sketch the before/after state of each pointer change.
- Save next before modifying: When reversing or reordering, always save
current.nextbefore you overwrite it. - Check null early: Handle
!headand!head.nextat the start to avoid null pointer errors. - Fast/slow is O(1) space: Prefer it over using a Set for cycle detection when asked for constant space.
- Recursion works but costs space: Recursive solutions use O(n) stack space, while iterative solutions use O(1).
Practice Problems
Ready to test your understanding? Try these LeetCode problems:
- Easy: Reverse Linked List, Merge Two Sorted Lists, Linked List Cycle, Remove Linked List Elements
- Medium: Remove Nth Node From End, Add Two Numbers, Reorder List, Sort List, Copy List with Random Pointer
- Hard: Merge k Sorted Lists, Reverse Nodes in k-Group
Practice with HireReady
Our platform includes 50+ linked list problems with visual pointer animations and spaced repetition to ensure long-term retention. Start practicing now!
Start Free Practice →Continue Learning
- Two Pointers Pattern - The fast/slow technique used extensively in linked lists
- Binary Search Guide - Arrays vs linked lists: when to use each
- Dynamic Programming Guide - Optimize recursive linked list solutions with memoization
- Start Practicing - Apply linked list patterns to real interview problems
Article Details
This guide is part of HireReady's interview prep library and is maintained to reflect current hiring practices.
Further Reading
Keep Reading
Trie Data Structure: Complete Interview Guide
Master the trie (prefix tree) for solving autocomplete, spell checking, and word search problems. Learn implementation, variations, and common interview patterns.
Read moreStack Pattern: Complete Interview Guide
Master stack patterns for coding interviews. Learn matching brackets, monotonic stack, expression evaluation, and common LeetCode problems.
Read moreBig O Notation Explained
Complete guide to Big O notation, algorithmic complexity, and performance analysis. Learn to calculate time and space complexity from code with real examples.
Read more