title: Reverse Linked List
slug: reverse-linked-list
difficulty: easy
leetcode_id: 206
leetcode_url: https://leetcode.com/problems/reverse-linked-list/
categories:
  - linked-lists
  - recursion
patterns:
  - linkedlist-reversal

function_signature: "def reverse_list(head: ListNode | None) -> ListNode | None:"

test_cases:
  visible:
    - input: { head: [1, 2, 3, 4, 5] }
      expected: [5, 4, 3, 2, 1]
    - input: { head: [1, 2] }
      expected: [2, 1]
    - input: { head: [] }
      expected: []
  hidden:
    - input: { head: [1] }
      expected: [1]
    - input: { head: [1, 2, 3] }
      expected: [3, 2, 1]

description: |
  Given the `head` of a singly linked list, reverse the list, and return *the reversed list*.

constraints: |
  - The number of nodes in the list is in the range `[0, 5000]`
  - `-5000 <= Node.val <= 5000`

examples:
  - input: "head = [1,2,3,4,5]"
    output: "[5,4,3,2,1]"
    explanation: "The list 1→2→3→4→5 becomes 5→4→3→2→1."
  - input: "head = [1,2]"
    output: "[2,1]"
    explanation: "The list 1→2 becomes 2→1."
  - input: "head = []"
    output: "[]"
    explanation: "An empty list remains empty when reversed."

explanation:
  intuition: |
    Imagine a chain of paper clips linked together, each pointing to the next. To reverse the chain, you need to make each clip point to the *previous* one instead of the next one.

    Think of it like this: for each node, we're changing the direction of its arrow. Originally `A → B → C`, we want `A ← B ← C`. The challenge is that when we change a node's `next` pointer to point backwards, we lose our reference to move forward!

    The solution is to use **three pointers**:
    - `prev`: The node we're pointing back to
    - `curr`: The node we're currently processing
    - `next_node`: Saved reference to the next node (so we don't lose it when we reverse the link)

    After processing all nodes, the original tail becomes the new head. Since `curr` ends up as `None` (past the last node), `prev` points to the last processed node — our new head.

  approach: |
    We solve this using an **Iterative Three-Pointer Approach**:

    **Step 1: Initialise pointers**

    - `prev = None`: The new tail will point to `None`
    - `curr = head`: Start at the beginning of the list
    - We don't initialise `next_node` yet — we'll set it inside the loop

    &nbsp;

    **Step 2: Iterate through the list**

    - While `curr` is not `None`:
      - **Save the next node**: `next_node = curr.next` (before we lose it!)
      - **Reverse the link**: `curr.next = prev` (point backwards)
      - **Move prev forward**: `prev = curr`
      - **Move curr forward**: `curr = next_node`

    &nbsp;

    **Step 3: Return the new head**

    - When the loop ends, `curr` is `None` (we've passed the last node)
    - `prev` points to what was the last node — now the first node
    - Return `prev` as the new head

    &nbsp;

    This processes each node exactly once with constant extra space.

  common_pitfalls:
    - title: Losing the Reference to the Next Node
      description: |
        If you reverse the link *before* saving the next node, you can't continue traversing!

        ```python
        # WRONG: We lose access to the rest of the list
        curr.next = prev
        curr = curr.next  # This now points backwards!
        ```

        Always save `next_node = curr.next` **before** modifying `curr.next`.
      wrong_approach: "curr.next = prev; curr = curr.next"
      correct_approach: "next_node = curr.next; curr.next = prev; curr = next_node"

    - title: Returning curr Instead of prev
      description: |
        After the loop, `curr` is `None` — we've moved past the end of the list. The actual new head is `prev`, which points to the last node we processed.

        Think about it: in the final iteration, `curr` points to the last node, we process it, then `curr = next_node` makes `curr = None`.
      wrong_approach: "return curr"
      correct_approach: "return prev"

    - title: Not Handling Empty or Single-Node Lists
      description: |
        The algorithm handles these edge cases naturally:
        - **Empty list**: `head = None`, loop never executes, return `prev = None`
        - **Single node**: Loop runs once, `prev` becomes the single node, which is returned

        No special-case code is needed, but it's good to trace through these cases to verify.
      wrong_approach: "Adding unnecessary if-checks for edge cases"
      correct_approach: "Trust the algorithm — it handles empty and single-node lists"

  key_takeaways:
    - "**Fundamental operation**: Reversing a linked list is used in many problems (palindrome check, reverse in groups, etc.)"
    - "**Three-pointer technique**: `prev`, `curr`, `next` is a common pattern for in-place linked list manipulation"
    - "**Save before modifying**: When changing pointers, always save references you'll need later"
    - "**Both iterative and recursive work**: Iterative is O(1) space, recursive is O(n) but more elegant — know both!"

  time_complexity: "O(n). We visit each of the n nodes exactly once, performing O(1) work at each."
  space_complexity: "O(1). We only use three pointer variables, regardless of list length."

solutions:
  - approach_name: Iterative
    is_optimal: true
    code: |
      class ListNode:
          def __init__(self, val=0, next=None):
              self.val = val
              self.next = next

      def reverse_list(head: ListNode | None) -> ListNode | None:
          prev = None  # Will become the new tail
          curr = head  # Start at the head

          while curr:
              # Save next node before we overwrite the link
              next_node = curr.next

              # Reverse the link: point backwards
              curr.next = prev

              # Move pointers forward
              prev = curr
              curr = next_node

          # prev is now the new head (curr is None)
          return prev
    explanation: |
      **Time Complexity:** O(n) — Single pass through all nodes.

      **Space Complexity:** O(1) — Only three pointers used.

      We iterate through the list once, reversing each link as we go. The key is saving the next node before modifying `curr.next`, then advancing both `prev` and `curr` forward.

  - approach_name: Recursive
    is_optimal: false
    code: |
      def reverse_list(head: ListNode | None) -> ListNode | None:
          # Base case: empty list or single node
          if not head or not head.next:
              return head

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

          # head.next is now the tail of the reversed sublist
          # Make it point back to head
          head.next.next = head

          # head is now the tail, so point it to None
          head.next = None

          # Return the new head (unchanged through recursion)
          return new_head
    explanation: |
      **Time Complexity:** O(n) — Each node processed once.

      **Space Complexity:** O(n) — Recursion stack depth equals list length.

      The recursive approach reverses the rest of the list first, then fixes the link for the current node. `head.next.next = head` makes the next node point back to us, and `head.next = None` makes us the new tail. The new head is propagated back through all recursive calls.
