← All patterns
Foundation · LL

Linked List

Pointer rewiring, cycles, reversal, and list structure.

9lessons
12worked problems
Freefull access
01 / 09

Introduction

A linked list is a linear data structure where each element (node) contains data and a reference (or pointer) to the next node in the sequence. Unlike arrays, linked lists don't require contiguous memory allocation, making insertions and deletions more efficient (O(1) when you have a reference to the node) at the cost of random access (O(n) instead of O(1)).

The most basic linked list is a singly linked list, where each node points only to the next node:

ListNode Definition

Intuitively, you want to imagine a linked list as a chain where each link (node) connects to the next link. The first node is called the "head" of the list, and the last node's next pointer is typically null, marking the end of the list.

textEXAMPLE
Head → [3] → [7] → [2] → [5] → None

The beauty and challenge of linked list problems come from manipulating these pointers to efficiently traverse, modify, or analyze the list.

pythonREFERENCE
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next
02 / 09

Key Techniques

Key Techniques for Solving Linked List Problems:

  1. Dummy Node Pattern: Creating a dummy/sentinel node at the start helps avoid edge cases
  1. Fast Slow (leading/lagging) Template: When you have one pointer that either starts ahead, or moves faster than another one that is lagging. This allows you to do things such as find the kth last node. Just create two pointers that differ in space by k, and when the leading node reaches the end, the lagging node is kth from the end.
  1. Iteration vs. Recursion: Many linked list problems can be solved either way
  1. Drawing Out the List: Visualizing the problem on paper often reveals the solution

Common Mistakes:
- Not handling edge cases (empty list, single node)
- Creating cycles accidentally
- Losing the head reference
- Off-by-one errors when counting positions

Basic Traversal

Let's walk through a simple example of traversing a linked list:

pythonREFERENCE
def traverse_linked_list(head):
    current = head
    while current:
        print(current.val)  # Process the current node
        current = current.next  # Move to the next node

The Dummy Node Template

The dummy node pattern is particularly useful for operations that might modify the head of the list:

pythonREFERENCE
def dummy_node_pattern(head):
    # Create a dummy node that points to the head
    dummy = ListNode(0)
    dummy.next = head

    # Now we can operate on the list without worrying about edge cases
    # where the head might change
    current = dummy

    # Your operations here...

    # Return the new head (which might have changed)
    return dummy.next
NOTE

This pattern is especially helpful when:
1. You need to insert/delete nodes at the beginning of the list
2. The head of the list might change
3. You need to return a new list

03 / 09

Basic Operations

Fundamental linked list operations including reversal and node removal.

WORKED PROBLEMS4
01Reverse Linked ListEasy

Given the head of a singly linked list, reverse the list, and return the new head.

Example:
- Input: 1→2→3→4→5→null
- Output: 5→4→3→2→1→null

This is the quintessential linked list problem. We need to reverse the direction of all pointers in the list.

pythonREFERENCE
# Iterative solution
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
    prev = None
    while head:
        head.next, prev, head = prev, head, head.next
    return prev

# Recursive solution
def reverseListRecursive(self, head: ListNode) -> ListNode:
    # Base case: empty list or list with only one node
    if not head or not head.next:
        return head

    # Recursively reverse the rest of the list after head
    new_head = self.reverseListRecursive(head.next)

    # The node after head now points back to head
    head.next.next = head

    # Head becomes the new tail, so it points to None
    head.next = None

    # Return the new head of the reversed list
    return new_head
TimeO(n)
SpaceO(1) iterative, O(n) recursive
WHY IT WORKS

To understand this, let's visualize the reversal process:

Initial state:

textEXAMPLE
None ← prev | current → [1] → [2] → [3] → None

First iteration:

textEXAMPLE
None ← [1] ← prev | current → [2] → [3] → None

Second iteration:

textEXAMPLE
None ← [1] ← [2] ← prev | current → [3] → None

Final state:

textEXAMPLE
None ← [1] ← [2] ← [3] ← prev | current → None

For the recursive case: just draw it out.

1 -> 2 -> 3

We have 1 and 3 -> 2.

We want to set 2 -> 1. However we only have access to the 1 and 3 right now. Iterating from one end of 3 to 2 will take O(n) time, which makes the recursion O(n^2).

But know that 1.next is STILL 2…
So we can access 2 through head.next, and we want to set head.next.next = head.
Now, we just need to get rid of 1 -> 2, by setting head.next = None. so now we have 3 -> 2 -> 1 -> None!

02Remove Nth Node From End of ListMedium

Given the head of a linked list, remove the nth node from the end of the list and return its head.

Example:
- Input: 1→2→3→4→5→null, n = 2
- Output: 1→2→3→5→null

This problem introduces the two-pointer technique:

pythonREFERENCE
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
    # Iterative solution using the two-pointer technique
    # Dummy node to handle edge cases
    dummy = ListNode(0)
    dummy.next = head

    # Two pointers: fast and slow
    fast = dummy
    slow = dummy

    # Move fast pointer n+1 steps ahead
    for _ in range(n + 1):
        fast = fast.next

    # Move both pointers until fast reaches the end
    while fast:
        slow = slow.next
        fast = fast.next

    # Remove the nth node from the end
    slow.next = slow.next.next

    return dummy.next

# Recursive solution
def removeNthFromEndRecursive(self, head: ListNode, n: int) -> ListNode:
    # We'll use a helper function that returns the position from the end
    def remove_helper(node, n):
        # Base case: we've reached the end of the list
        if not node:
            return 0

        # Recursively process the rest of the list
        position = remove_helper(node.next, n) + 1

        # If this is the (n+1)th node from the end, remove the nth node
        if position == n + 1:
            node.next = node.next.next

        return position

    # Create a dummy node to handle edge cases
    dummy = ListNode(0)
    dummy.next = head

    # Start the recursive process
    remove_helper(dummy, n)

    return dummy.next
TimeO(n)
SpaceO(1) iterative, O(n) recursive
WHY IT WORKS

The key insight is that by having the fast pointer n+1 steps ahead of the slow pointer, when the fast pointer reaches the end, the slow pointer will be at the node just before the one we want to remove. (instead of fast, slow, leading and lagging are better names because they travel at the same speed, they just start at different places) The dummy node helps handle the edge case of removing the first node. Try some examples.

03Middle of the Linked ListEasy

Given a non-empty, singly linked list, return a middle node of the linked list. If there are two middle nodes, return the second middle node.

Example:
- Input: [1,2,3,4,5]
- Output: [3,4,5] (The middle node is 3)

This is a classic application of the fast and slow pointer technique:

pythonREFERENCE
def middleNode(self, head: ListNode) -> ListNode:
    # Iterative solution using fast and slow pointers
    # Initialize slow and fast pointers
    slow = head
    fast = head

    # Move slow one step and fast two steps at a time
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next

    # When fast reaches the end, slow is at the middle
    return slow

# Recursive solution using a two-pass approach
def middleNodeRecursive(self, head: ListNode) -> ListNode:
    # First pass: count the number of nodes
    def count_nodes(node):
        if not node:
            return 0
        return 1 + count_nodes(node.next)

    # Second pass: find the middle node
    def find_middle(node, current, target):
        if current == target:
            return node
        return find_middle(node.next, current + 1, target)

    # Count the nodes and calculate the middle position
    length = count_nodes(head)
    middle_pos = length // 2  # Integer division gives the second middle for even lengths

    # Find the middle node
    return find_middle(head, 0, middle_pos)
TimeO(n)
SpaceO(1) iterative, O(n) recursive
WHY IT WORKS

When fast pointer moves at twice the speed of slow, by the time fast reaches the end, slow will be at the middle. For even-length lists, this gives us the second of the two middle nodes, as required.

04Delete Node in a Linked ListMedium

Write a function to delete a node in a singly-linked list. You will not be given access to the head of the list, instead you will be given direct access to the node to be deleted.

Example:
- Input: head = [4,5,1,9], node = 5
- Output: [4,1,9]

This problem has an interesting twist:

pythonREFERENCE
def deleteNode(self, node):
    # Since we don't have access to the previous node,
    # we can't delete the node directly.
    # Instead, we'll copy the next node's value to
    # the current node and delete the next node

    # Copy the next node's value
    node.val = node.next.val

    # Delete the next node
    del_node = node.next
    node.next = node.next.next
    del del_node
TimeO(1)
SpaceO(1)
WHY IT WORKS

This solution is clever because it bypasses the need to access the previous node. Instead, we copy the value from the next node and delete that node instead.

Ex: 1 -> 2 -> 3 -> 4

We have access to 2.

We do not have access to 1, so what we can do is:

1 -> 3 -> 3 -> 4

1 -> 3 -> 4

Note, for proper node management we should delete the original 3 node.

04 / 09

Arithmetic Operations

Problems involving arithmetic operations on numbers represented as linked lists.

WORKED PROBLEMS2
01Add Two NumbersMedium

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each node contains a single digit. Add the two numbers and return the sum as a linked list.

Example:
- Input: l1 = [2,4,3], l2 = [5,6,4]
- Output: [7,0,8]
- Explanation: 342 + 465 = 807

pythonREFERENCE
def addTwoNumbers(self, l1, l2):
    dummy = curr = ListNode(0)
    carry = 0
    while l1 or l2 or carry:
        carry, val = divmod(
            (l1.val if l1 else 0) + (l2.val if l2 else 0) + carry, 10)
        curr.next = ListNode(val)
        curr = curr.next
        if l1: l1 = l1.next
        if l2: l2 = l2.next
    return dummy.next

# Recursive solution
def addTwoNumbersRecursive(self, l1: ListNode, l2: ListNode, carry=0) -> ListNode:
    # Base case: if both lists are empty and no carry
    if not l1 and not l2 and not carry:
        return None

    # Get values (or 0 if the list has ended)
    x = l1.val if l1 else 0
    y = l2.val if l2 else 0

    # Calculate sum and new carry
    total = x + y + carry
    carry = total // 10
    digit = total % 10

    # Create a new node with the digit value
    result = ListNode(digit)

    # Recursively process the next digits
    result.next = self.addTwoNumbersRecursive(
        l1.next if l1 else None,
        l2.next if l2 else None,
        carry
    )

    return result
TimeO(max(n, m))
SpaceO(max(n, m))
WHY IT WORKS

Note that dummy = curr = ListNode(0) is equivalent to two lines:

pythonEXAMPLE
curr = ListNode(0)
dummy = curr

NOT

pythonEXAMPLE
curr = ListNode(0)
dummy = ListNode(0)

This is basically just simulating addition of two integers as learned in primary school. We can use divmod to get the quotient and remainder.

02Add Two Numbers IIMedium

You are given two non-empty linked lists representing two non-negative integers. The most significant digit comes first and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

pythonREFERENCE
def reverse(self, curr):
    prev = None
    while curr:
        curr.next, prev, curr = prev, curr, curr.next
    return prev

def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
    return self.reverse(self.addTwoNumbersI(self.reverse(l1), self.reverse(l2)))

# there are stored left to right...
# can reduce to Add Two Numbers I, by reversing both.
# and then reverse the answer
TimeO(max(n, m))
SpaceO(max(n, m))
WHY IT WORKS

This is a reminder: try to make connections with similar problems you've solved in the past. Reduction is a powerful problem solving technique, which involves massaging the current problem X in order to provide the correct inputs to a known problem solution Y, and then massaging the output of Y to solve X.

05 / 09

Merging Lists

Problems involving merging sorted linked lists.

WORKED PROBLEMS2
01Merge Two Sorted ListsEasy

Merge two sorted linked lists and return it as a sorted list.

Example:
- List1: 1→2→4→null
- List2: 1→3→4→null
- Output: 1→1→2→3→4→4→null

This is a great place to apply our dummy node pattern:

pythonREFERENCE
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
    # Iterative solution using dummy node pattern
    # Create a dummy node
    dummy = ListNode(0)
    current = dummy

    # Compare nodes from both lists and link the smaller one
    while l1 and l2:
        if l1.val <= l2.val:
            current.next = l1
            l1 = l1.next
        else:
            current.next = l2
            l2 = l2.next
        current = current.next

    # Link the remaining nodes (if any)
    current.next = l1 if l1 else l2

    # Return the merged list (excluding the dummy node)
    return dummy.next

# Recursive solution
def mergeTwoListsRecursive(self, l1: ListNode, l2: ListNode) -> ListNode:
    # Base cases
    if not l1:
        return l2
    if not l2:
        return l1

    # Recursive case: determine which node should come first
    if l1.val <= l2.val:
        # l1 comes first, so l1.next should be merged with l2
        l1.next = self.mergeTwoListsRecursive(l1.next, l2)
        return l1
    else:
        # l2 comes first, so l2.next should be merged with l1
        l2.next = self.mergeTwoListsRecursive(l1, l2.next)
        return l2
TimeO(n + m)
SpaceO(1) iterative, O(n + m) recursive
WHY IT WORKS

The dummy node pattern shines here because it lets us avoid checking for edge cases like empty lists. We simply connect nodes in sorted order and return the result starting from dummy.next. This is exactly the same as merging two arrays into 1 sorted list, but just operating on a linked list data structure.

02Merge k Sorted ListsHard

You are given an array of k linked-lists, each linked-list is sorted in ascending order. Merge all the linked-lists into one sorted linked-list and return it.

Example:
- Input: lists = [[1→4→5], [1→3→4], [2→6]]
- Output: [1→1→2→3→4→4→5→6]

This problem can be thought of as an extension of our earlier "Merge Two Sorted Lists" problem. We have several approaches:

pythonREFERENCE
# Approach 1: Sequential Merging
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
    if not lists:
        return None

    result = lists[0]
    # Sequentially merge each list with the result
    for i in range(1, len(lists)):
        result = self.mergeTwoLists(result, lists[i])

    return result
# Time: O(N*k), Space: O(1)

# Approach 2: Divide and Conquer (More Efficient)
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
    if not lists:
        return None

    # Merge lists using divide and conquer
    def merge(lists, start, end):
        if start == end:
            return lists[start]
        if start > end:
            return None

        mid = start + (end - start) // 2
        left = merge(lists, start, mid)
        right = merge(lists, mid + 1, end)
        return mergeTwoLists(left, right)

    return merge(lists, 0, len(lists) - 1)
# Time: O(N*log(k)), Space: O(log(k))

# Approach 3: Using a Priority Queue (Heap)
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
    pq = [(node.val, i, node) for i, node in enumerate(lists) if node]
    heapify(pq)
    dummy = ListNode(-1)
    curr = dummy

    while pq:
        _, i, top_node = heappop(pq)

        curr.next = top_node
        curr = top_node

        if top_node.next:
            heappush(pq, (top_node.next.val, i, top_node.next))

    return dummy.next
# Time: O(N*log(k)), Space: O(k)
TimeO(N*log(k))
SpaceO(k) for heap approach
WHY IT WORKS

Approach 1 is intuitive but not optimal, as we repeatedly scan through the growing result list.

Approach 2 T(k) = 2*T(k//2) + O(n) => O(nlogk). This approach is much more efficient as it pairs up lists and merges them, reducing the number of comparisons. You should read the divide and conquer section first before trying to understand this solution. Once you read that section, this logic is straightforward. If we merge all lists on the left half into one list, and all lists on the right half into one list, we can merge them together using the mergeTwoLists function from the last question. Nice.

Approach 3 efficiently merges all lists by always taking the smallest node available across all lists. The priority queue gives us the smallest element in log(k) time. Note that we don't use i, but we need it in the tuple as the second argument, otherwise heapify will complain since it can't compare list nodes using '<'. (try removing it from the tuple)

The key insight in this problem is the efficiency gain from using either divide and conquer or a priority queue, which reduces the time complexity from O(Nk) to O(Nlog(k)). For large values of k, this makes a significant difference.

06 / 09

List Manipulation

Problems involving rotation, partitioning, and partial reversal of linked lists.

WORKED PROBLEMS3
01Rotate ListMedium

Given the head of a linked list, rotate the list to the right by k places.

Example:
- Input: 1→2→3→4→5→null, k = 2
- Output: 4→5→1→2→3→null

This problem requires several key steps: finding the length of the list, connecting the tail to the head to form a cycle, then breaking the cycle at the right place.

pythonREFERENCE
def rotateRight(self, head: ListNode, k: int) -> ListNode:
    # Edge cases
    if not head or not head.next or k == 0:
        return head

    # Step 1: Find the length of the list and the tail node
    current = head
    length = 1
    while current.next:
        current = current.next
        length += 1

    # The last node (tail) is now in current
    tail = current

    # Step 2: Calculate the effective rotation
    # If k = length, we end up with the original list
    k = k % length
    if k == 0:
        return head

    # Step 3: Form a cycle by connecting tail to head
    tail.next = head

    # Step 4: Find the new tail position
    # We need to go to the (length - k)th node
    current = head
    for _ in range(length - k - 1):
        current = current.next

    # Step 5: Break the cycle at the right position
    new_head = current.next
    current.next = None

    return new_head
TimeO(n)
SpaceO(1)
WHY IT WORKS

The key insight is that rotation by k places is equivalent to taking the last k nodes and moving them to the front. By connecting the tail to the head, we form a cycle, then break it at the appropriate position.

This pattern of "connect to form a cycle, then break" is useful in many linked list rotation and reordering problems.

02Partition ListMedium

Given the head of a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x. You should preserve the original relative order of the nodes in each of the two partitions.

Example:
- Input: 1→4→3→2→5→2→null, x = 3
- Output: 1→2→2→4→3→5→null

For this problem, we can maintain two separate lists (one for smaller values, one for greater/equal values) and then merge them at the end:

pythonREFERENCE
def partition(self, head: ListNode, x: int) -> ListNode:
    # Create dummy heads for two separate lists
    smaller_dummy = ListNode(0)
    greater_dummy = ListNode(0)

    # Pointers to track the current end of each list
    smaller = smaller_dummy
    greater = greater_dummy

    # Traverse the original list
    current = head
    while current:
        if current.val < x:
            # Add to smaller list
            smaller.next = current
            smaller = smaller.next
        else:
            # Add to greater/equal list
            greater.next = current
            greater = greater.next

        # Move to next node
        current = current.next

    # Connect the two lists
    greater.next = None  # Important: prevent cycles!
    smaller.next = greater_dummy.next

    return smaller_dummy.next
TimeO(n)
SpaceO(1)
WHY IT WORKS

The key insight is to build two separate lists in a single pass through the original list, then connect them. The dummy nodes make it easy to handle edge cases, and setting greater.next = None is crucial to prevent cycles in the final list.

This pattern of "build separate lists, then connect" is useful for many partitioning or reordering problems in linked lists.

03Reverse Linked List IIMedium

Given the head of a linked list and two positions left and right, reverse the nodes of the list from position left to position right, and return the reversed list.

Example:
- Input: 1→2→3→4→5→null, left = 2, right = 4
- Output: 1→4→3→2→5→null

This problem requires careful pointer manipulation to reverse only a portion of the list:

pythonREFERENCE
def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
    dummy = ListNode(-1, head)  # NEED THE DUMMY!!! WHAT IF WE REVERSE THE ENTIRE LIST!!!

    prev, curr = dummy, head
    for _ in range(left - 1):
        curr, prev = curr.next, curr

    prev_node_before_reverse = prev  # 1
    first_node_reversed = curr  # 2

    for _ in range(right - left + 1):
        curr.next, curr, prev = prev, curr.next, curr

    first_node_after_reverse = curr  # 5
    last_node_reversed = prev  # 4

    prev_node_before_reverse.next = last_node_reversed
    first_node_reversed.next = first_node_after_reverse

    return dummy.next
TimeO(n)
SpaceO(1)
WHY IT WORKS

This "in-place partial reversal" technique is elegant because it avoids creating any new nodes and requires only one pass through the list.

Walk through the first example and this will be clear. Basically we just walk up to before we reverse the list. The 4 important nodes are: the node immediately before reversal, (index left -1) the first node being reversed (index left), the last node being reversed (index right), and the node immediately after reversal. (index right+1) We need to store these, reverse the list, and then re-wire accordingly at the end of the reversal.

pythonEXAMPLE
prev_node_before_reverse.next = last_node_reversed
first_node_reversed.next = first_node_after_reverse
07 / 09

Copy Operations

Problems involving deep copying of linked lists with complex pointer structures.

WORKED PROBLEMS1
01Copy List with Random PointerMedium

A linked list of length n is given such that each node contains an additional random pointer, which could point to any node in the list, or null. Construct a deep copy of the list.

Example:

textEXAMPLE
Input:
1 → 2 → 3 → null
↓   ↓   ↓
3   1   2

where the second row indicates where the random pointer points

Output: A deep copy with the same structure

pythonREFERENCE
"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""

# KEY IDEA: use map between nodes in the original list and nodes in the
# copied list for fast O(1) setting of random ptrs
# O(n) time, O(n) space

# step 1. copy the new linked list with only values and next ptrs
# step 2. fill in the random pointers for the new linked list

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if not head: return head
        old_to_new_map = {}
        og_head = head
        prev_new_node = None
        curr_old_node = head

        while curr_old_node:
            new_node = Node(curr_old_node.val)
            if prev_new_node:
                prev_new_node.next = new_node
            prev_new_node = new_node
            old_to_new_map[curr_old_node] = new_node
            curr_old_node = curr_old_node.next

        # fill in random nodes now
        curr_old_node = og_head
        while curr_old_node:
            if curr_old_node.random:
                old_to_new_map[curr_old_node].random = old_to_new_map[curr_old_node.random]
            curr_old_node = curr_old_node.next

        return old_to_new_map[og_head]
TimeO(n)
SpaceO(n)
WHY IT WORKS

You can't just copy the list naively, because how do you copy the random pointers? You need a map between old nodes and new nodes in order to point the random pointers to the correct new node.

The idea is simple, we maintain an old to new node map. We first iterate over the old nodes, creating new nodes and setting the .next pointers of the new nodes, and updating the map.

Once the map is filled, we can fill in the random nodes. We iterate over the old nodes again, and if there is a random pointer, we set the new nodes random pointer to the new node that corresponds to the old nodes random node.

Note there is another solution that involves interleaving, but it is more complex. This solution is generic to the copy graph problem as well involving BFS so I recommend learning this.

08 / 09

Doubly Linked Lists

While we've focused on singly linked lists, many problems involve doubly linked lists, where each node contains pointers to both the next and previous nodes:

KEY INSIGHT

Advantages of Doubly Linked Lists:

  1. Bidirectional traversal: Can move forward and backward through the list
  2. Efficient deletion: Can delete a node in O(1) time without needing a separate reference to the previous node
  3. LRU cache implementation: Forms the basis for least recently used (LRU) cache designs

Common Problems With Doubly Linked Lists:

  • LRU Cache (LeetCode 146) A doubly linked list combined with a hash map enables O(1) operations for an LRU cache:
  • The list maintains the order of elements by recency
  • The hash map enables direct access to nodes
  1. Flatten Multilevel Doubly Linked List (LeetCode 430) Converting a nested structure to a flattened doubly linked list while preserving connections.
  1. Design Browser History (LeetCode 1472) Implementing back and forward functionality using a doubly linked list.

The key insight with doubly linked lists is that they trade additional memory (for the extra prev pointer) for operational flexibility. This makes them ideal for problems where bidirectional movement is required or where you need to efficiently remove nodes without traversing from the head.

Definition

Visualizing a doubly linked list:

textEXAMPLE
None ← [3] ⇄ [7] ⇄ [2] ⇄ [5] → None
    ↑head
pythonREFERENCE
class DoublyListNode:
    def __init__(self, val=0, next=None, prev=None):
        self.val = val
        self.next = next
        self.prev = prev

Insertion

pythonREFERENCE
def insert_after(self, existing_node, new_value):
    new_node = DoublyListNode(new_value)

    # Connect new_node to its neighbors
    new_node.next = existing_node.next
    new_node.prev = existing_node

    # Update neighbors to connect to new_node
    if existing_node.next:
        existing_node.next.prev = new_node
    existing_node.next = new_node

Deletion

pythonREFERENCE
def delete_node(self, node_to_delete):
    # Connect the node's neighbors to each other
    if node_to_delete.prev:
        node_to_delete.prev.next = node_to_delete.next
    if node_to_delete.next:
        node_to_delete.next.prev = node_to_delete.prev

    # If deleting the head, update head reference
    if self.head == node_to_delete:
        self.head = node_to_delete.next
09 / 09

Iterative vs Recursive

When solving linked list problems, it's important to understand the tradeoffs between iterative and recursive solutions:

AspectIterativeRecursive
Space ComplexityO(1) for most problemsO(n) due to call stack
ReadabilityMore explicit, sometimes verboseOften more concise and elegant
PerformanceGenerally faster in practiceCall stack overhead may slow things down
Edge CasesRequires explicit handlingBase cases handle many edges
DebuggingEasier to trace and debugCan be trickier to follow mentally
NEXT PATTERNTree