โ† Back to DevBytes

Solving Reverse a Linked List in Python: Step-by-Step Guide

Solving Reverse a Linked List in Python: Step-by-Step Guide

Reversing a linked list is one of the most classic problems in computer science and a frequent guest in technical interviews. While the problem sounds simple on the surface, it forces you to deeply understand pointers, references, and how nodes connect to one another. In this tutorial, we will walk through what a linked list is, why reversing it matters, and how to implement the solution in Python using both iterative and recursive approaches.

What Is a Linked List?

A linked list is a linear data structure where elements, called nodes, are stored in non-contiguous memory locations. Each node contains two parts: a value (the data) and a next reference (a pointer to the next node in the sequence). Unlike arrays, linked lists do not provide random access to elements, which means traversal must happen sequentially starting from the head node.

Here is a minimal definition of a singly linked list node in Python:

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

Given a list like 1 -> 2 -> 3 -> 4 -> None, reversing it means producing 4 -> 3 -> 2 -> 1 -> None. The head of the original list becomes the tail, and the original tail becomes the new head.

Why Reversing a Linked List Matters

You might wonder why this specific operation deserves so much attention. There are several reasons:

How to Reverse a Linked List Iteratively

The iterative approach is the most common and efficient solution. The idea is to traverse the list once while flipping each node's next pointer to point to the previous node instead of the next one. We maintain three pointers during traversal: prev, current, and next_node.

The algorithm works as follows:

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next


def reverse_list(head):
    prev = None
    current = head

    while current is not None:
        next_node = current.next   # store the next node
        current.next = prev        # reverse the pointer
        prev = current             # move prev forward
        current = next_node        # move current forward

    return prev  # prev is the new head


# Helper to build a list from a Python list
def build_list(values):
    dummy = ListNode()
    tail = dummy
    for v in values:
        tail.next = ListNode(v)
        tail = tail.next
    return dummy.next


# Helper to print a list
def print_list(head):
    result = []
    while head:
        result.append(str(head.val))
        head = head.next
    print(" -> ".join(result) + " -> None")


# Example usage
head = build_list([1, 2, 3, 4, 5])
print("Original:")
print_list(head)

reversed_head = reverse_list(head)
print("Reversed:")
print_list(reversed_head)

Running this code produces:

Original:
1 -> 2 -> 3 -> 4 -> 5 -> None
Reversed:
5 -> 4 -> 3 -> 2 -> 1 -> None

The iterative solution runs in O(n) time, where n is the number of nodes, and uses O(1) extra space because we only store a few pointers regardless of list size.

How to Reverse a Linked List Recursively

The recursive approach is elegant but slightly trickier. The idea is to recursively reach the end of the list, then on the way back up the call stack, reverse each pointer. The base case returns the last node, which becomes the new head. As each recursive call returns, we set current.next.next = current to flip the pointer, and then set current.next = None to break the old link.

def reverse_list_recursive(head):
    # Base case: empty list or single node
    if head is None or head.next is None:
        return head

    # Recursively reverse the rest of the list
    new_head = reverse_list_recursive(head.next)

    # Reverse the pointer between the next node and current node
    head.next.next = head
    head.next = None

    return new_head


# Example usage
head = build_list([1, 2, 3, 4, 5])
print("Original:")
print_list(head)

reversed_head = reverse_list_recursive(head)
print("Reversed (recursive):")
print_list(reversed_head)

The recursive version also runs in O(n) time, but it uses O(n) space on the call stack due to the recursive calls. For very long lists, this can cause a stack overflow, which is why the iterative approach is generally preferred in production code.

Handling Edge Cases

A robust solution must handle several edge cases gracefully. Always test your implementation against the following scenarios:

Here is a quick test harness that covers these cases:

def test_reverse():
    # Empty list
    assert reverse_list(None) is None

    # Single node
    single = ListNode(1)
    result = reverse_list(single)
    assert result.val == 1 and result.next is None

    # Two nodes
    two = build_list([1, 2])
    result = reverse_list(two)
    assert result.val == 2 and result.next.val == 1 and result.next.next is None

    # General case
    general = build_list([1, 2, 3, 4, 5])
    result = reverse_list(general)
    assert result.val == 5

    print("All tests passed.")


test_reverse()

Best Practices

When implementing or using linked list reversal in real projects, keep the following best practices in mind:

Variations You May Encounter

Once you understand the basic reversal, several related problems become much easier:

Here is a brief example of reversing a doubly linked list for reference:

class DoublyListNode:
    def __init__(self, val=0, prev=None, next=None):
        self.val = val
        self.prev = prev
        self.next = next


def reverse_doubly_list(head):
    current = head
    new_head = None

    while current is not None:
        # Swap prev and next pointers
        current.prev, current.next = current.next, current.prev
        new_head = current
        # Because we swapped, move to the old next (now in prev)
        current = current.prev

    return new_head

Conclusion

Reversing a linked list is a deceptively simple problem that teaches fundamental lessons about pointer manipulation, memory references, and algorithm design. The iterative solution is efficient, easy to reason about, and safe for lists of any size, while the recursive solution offers an elegant alternative that reinforces your understanding of the call stack. By mastering both approaches, handling edge cases carefully, and following best practices, you will be well prepared not only for coding interviews but also for the many real-world scenarios where linked list manipulation plays a role. Practice the variations mentioned above to deepen your skills, and always remember to trace through small examples by hand before trusting your code on larger inputs.

๐Ÿ›  Tools from DevBytes

Inventory Tracker Pro โ€” Excel inventory system, low-stock alerts ยท $19
AI Dev Kit for Mac โ€” local AI dev environment templates ยท $9.99
KeyMapper for Mac โ€” custom keyboard shortcut toolkit ยท $7.99

โ† Back to all articles