Problem Overview — Swap Every Two Adjacent Nodes
LeetCode 24 Swap Nodes in Pairs gives you the head of a singly linked list and asks you to swap every two adjacent nodes and return the head of the modified list. If the list has an odd number of nodes, the last unpaired node stays in place. The critical constraint is that you must change the actual node links — you are not allowed to simply swap node values.
The no-value-swap requirement is the heart of the problem. In an interview it is a signal that the interviewer wants to test your ability to rewire pointers carefully, not just shuffle integers. Changing values would trivialize the problem; changing links forces you to manage three or four pointers per pair and understand how the head changes after the first swap.
Understanding the edge cases up front helps build a correct solution immediately. An empty list or a single-node list both require no swaps and the head is returned as-is. A two-node list is the smallest non-trivial case: one swap produces a new head. A list of exactly four nodes produces two swaps. These small cases are also the best tests for boundary conditions in your code.
- The number of nodes in the list is in the range [0, 100]
- 0 <= Node.val <= 100
- You must swap nodes by changing links, not by swapping node values
- If the list has an odd number of nodes, the final node remains in its position
- Return the head of the list after all pairwise swaps are complete
Iterative with Dummy Node — The Prev Pointer Pattern
The iterative approach introduces a dummy node that sits before the head of the list. A prev pointer starts at the dummy node. At each iteration, first is prev.next (the first node of the current pair) and second is first.next (the second node). The three rewirings are: prev.next = second, first.next = second.next, second.next = first. After the swap, advance prev to first (which is now the second node in its new position) to set up the next iteration.
The loop continues as long as prev.next and prev.next.next are both non-null — meaning there is a full pair remaining. If only one node is left, the loop exits and that node stays in place. The final result is dummy.next, which after the first swap points to the new head of the list.
After all iterations, return dummy.next as the new head of the swapped list. The dummy node was never inserted into the actual list — it was only used to anchor the prev pointer so that the first pair is handled identically to all subsequent pairs. The time complexity is O(n) since each node is visited once; space complexity is O(1) since no extra data structures are used.
The Dummy Node Handles the Head Swap Uniformly
The dummy node eliminates the special case of swapping the first pair. Without it, the new head after the first swap would need separate handling. With dummy.next set to head, the prev pointer always points to the node before the current pair — even for the very first pair — so all three rewirings are identical on every iteration regardless of position in the list.
Iterative Step-by-Step — Three Pointer Rewirings Per Pair
Each iteration of the loop performs exactly three pointer reassignments to swap a pair. Before any reassignment, save references: first = prev.next and second = first.next. These saved references are essential — once you start rewiring, following pointers will lead to different nodes than you expect if you have not captured them first.
The three rewirings in order: (1) prev.next = second — the node before the pair now points to the second node, which becomes the new front of the pair; (2) first.next = second.next — the first node (now the tail of the pair) points past second to the rest of the list; (3) second.next = first — the second node (now the head of the pair) points to first, completing the swap. The order matters: step 1 must come before step 3, and step 2 must use the original second.next value before step 3 overwrites it.
After the three rewirings, advance prev = first. Since first is now the second node in the swapped pair (the tail of the pair), setting prev = first positions the prev pointer directly before the next unprocessed pair. The loop condition prev.next != null and prev.next.next != null is then re-evaluated to decide whether another full pair exists.
- 1Create dummy node; dummy.next = head; prev = dummy
- 2Loop while prev.next and prev.next.next are not null
- 3Save: first = prev.next; second = first.next
- 4Rewire step 1: prev.next = second
- 5Rewire step 2: first.next = second.next
- 6Rewire step 3: second.next = first
- 7Advance: prev = first (first is now the tail of the swapped pair)
- 8Return dummy.next as the new head
Recursive Approach — Swap One Pair and Delegate the Rest
The recursive approach is elegantly concise. The base case returns head immediately when head is null or head.next is null — handling empty lists, single-node lists, and the last unpaired node of an odd-length list without any special logic. In the recursive case, save second = head.next. Recurse on second.next to get the already-swapped tail. Then connect: head.next = recurse(second.next), second.next = head. Return second as the new head of this pair.
The recursion depth equals the number of pairs, which is n/2. For a list of 100 nodes the stack depth is 50 frames — well within normal limits for the given constraint of at most 100 nodes. Each recursive call processes exactly one pair and returns the new head of that pair. The return value propagates up the call stack, with each level connecting its pair to the already-processed result.
The recursive solution produces identical output to the iterative one. Both are O(n) time. The difference is space: iterative is O(1) space while recursive uses O(n/2) = O(n) call stack space. In an interview, implementing both and explaining the tradeoff demonstrates full command of the problem.
Recursive Elegance: Four Lines of Logic
The recursive approach reduces to four lines: (1) base case — return head if head is null or head.next is null; (2) save second = head.next; (3) connect head.next = swapPairs(second.next) and second.next = head; (4) return second. It naturally handles odd-length lists because the deepest recursive call hits the base case for the trailing unpaired node and returns it unchanged, letting the connection chain propagate back correctly.
Walk-Through Example — Tracing 1→2→3→4
Start with the list 1→2→3→4. The dummy node is prepended: dummy→1→2→3→4. Prev = dummy. Iteration 1: first = 1, second = 2. After rewirings: prev.next = 2, first.next = 3, second.next = 1. List becomes dummy→2→1→3→4. Prev advances to node 1 (the tail of the first pair).
Iteration 2: first = 3, second = 4. After rewirings: prev.next = 4, first.next = null, second.next = 3. List becomes dummy→2→1→4→3. Prev advances to node 3 (the tail of the second pair). Loop condition: prev.next is null — exit loop. Return dummy.next = node 2.
For the recursive trace of the same input: swapPairs(1) calls swapPairs(3) first (recursing on second.next = 3). swapPairs(3) calls swapPairs(null) which returns null. Back in swapPairs(3): head=3, second=4, head.next=null, second.next=3, return 4. Back in swapPairs(1): head=1, second=2, head.next=4→3 (from recursion), second.next=1, return 2. Final: 2→1→4→3.
- 1Input: dummy→1→2→3→4; prev=dummy
- 2Iter 1: first=1, second=2; rewire: 2→1→3→4; prev=1
- 3Iter 2: first=3, second=4; rewire: 4→3→null; prev=3
- 4Exit loop (no more full pairs); return dummy.next = 2
- 5Output: 2→1→4→3
- 6Recursive trace: swapPairs(1) → swapPairs(3) → swapPairs(null)=null → returns 4→3 → connects 1.next=4→3, 2.next=1 → returns 2
Code Walkthrough — Python and Java, Iterative and Recursive
Python iterative: def swapPairs(head): dummy = ListNode(0); dummy.next = head; prev = dummy. While prev.next and prev.next.next: first = prev.next; second = first.next; prev.next = second; first.next = second.next; second.next = first; prev = first. Return dummy.next. O(n) time, O(1) space. Python recursive: def swapPairs(head): if not head or not head.next: return head. second = head.next; head.next = swapPairs(second.next); second.next = head; return second. O(n) time, O(n) stack space.
Java iterative: public ListNode swapPairs(ListNode head) { ListNode dummy = new ListNode(0); dummy.next = head; ListNode prev = dummy; while (prev.next != null && prev.next.next != null) { ListNode first = prev.next; ListNode second = first.next; prev.next = second; first.next = second.next; second.next = first; prev = first; } return dummy.next; }. Java recursive: public ListNode swapPairs(ListNode head) { if (head == null || head.next == null) return head; ListNode second = head.next; head.next = swapPairs(second.next); second.next = head; return second; }.
Both iterative solutions run in O(n) time with O(1) extra space — only a constant number of pointers are maintained regardless of list length. Both recursive solutions run in O(n) time with O(n/2) stack space due to the call depth. The iterative solution is preferred in production code where stack overflow risk must be avoided; the recursive solution is preferred in interviews for its clarity and conciseness.
Save References Before Rewiring — Lost Pointer Pitfall
In the iterative approach, you must save first = prev.next and second = first.next before any rewiring begins. Once you execute prev.next = second, following prev.next no longer leads to first — it leads to second. If you did not save the reference to first beforehand, it is lost and the rewiring cannot be completed correctly. Always capture all necessary node references at the start of each iteration before modifying any pointer.