Reverse a singly linked list in Swift, iteratively and recursively. What can go wrong with long lists?
Predict first
What does the iterative and the recursive reversal print?
final class Node {
var val: Int
var next: Node?
init(_ val: Int, _ next: Node? = nil) { self.val = val; self.next = next }
}
func reverse(_ head: Node?) -> Node? {
var prev: Node? = nil, cur = head
while let c = cur {
let next = c.next // remember the rest before overwriting the link
c.next = prev
prev = c
cur = next
}
return prev
}
func reverseRecursive(_ head: Node?) -> Node? {
guard let h = head, let n = h.next else { return head }
let newHead = reverseRecursive(n)
n.next = h
h.next = nil
return newHead
}
func list(_ a: [Int]) -> Node? { var h: Node? = nil; for v in a.reversed() { h = Node(v, h) }; return h }
func show(_ h: Node?) -> String {
var out: [String] = [], c = h
while let n = c { out.append(String(n.val)); c = n.next }
return out.isEmpty ? "empty" : out.joined(separator: " -> ")
}
print(show(reverse(list([1, 2, 3, 4, 5]))))
print(show(reverse(list([7]))))
print(show(reverse(list([]))))
print(show(reverseRecursive(list([1, 2, 3]))))
- 5 -> 4 -> 3 -> 2 -> 1 / 7 / empty / 3 -> 2 -> 1
- 1 -> 2 -> 3 -> 4 -> 5 / 7 / empty / 3 -> 2 -> 1
- 5 -> 4 -> 3 -> 2 -> 1 / 7 / crash / 3 -> 2 -> 1
Mental model. Turning a train around car by car: you unhook each car, hook it to the end of the reversed part, and only then move to the next.
Iteratively, walk the list with three references: prev (starts nil), cur and a saved next. For each node, remember its successor, point it back at prev, then advance both: the invariant is that everything before cur is already reversed and hangs off prev. When cur becomes nil, prev is the new head. O(n) time, O(1) memory. The recursive version reverses the rest, then makes the second node point back at the first and cuts the first node’s own link; it is shorter to say but uses O(n) stack.
Say what happens to edge cases (empty list and a single node return as they are, which the check shows) and to depth. A recursion over a list of a million nodes overflows the stack (a debug build of the recursive version crashed with a segmentation fault when run here), which is why the iterative version is the safe default. Releasing a very long chain of final class nodes is also recursive in principle, since releasing the head releases the next node; on this toolchain a ten-million-node chain was released without a crash, so measure before you add the usual workaround of unlinking nodes in a loop in deinit. Prefer the iterative version in interviews, and use final class Node with var next: Node?. Reported at Meta (anonymous, unverified).
The links flip one by one; a single node and the empty list come back unchanged.
Common traps
- Overwriting
c.nextbefore saving it, which loses the rest of the list. - Returning the old head instead of
prev.
They might follow up with
- How do you reverse only a section from position m to n? Walk to m, reverse n - m + 1 nodes, and reconnect both ends.
- How do you detect a cycle? Two pointers, one moving twice as fast; they meet inside a cycle.
Related questions
- 29.2.2 Implement a hash map from scratch (get, set, remove), then extend it: what do you test and how does resizing…
- 29.2.4 Return the values you see when looking at a binary tree from its right side. How does a level-order walk give…
- 29.2.7 Given function entry and exit events with timestamps, print the time spent in each function when it exits.…
Implement a hash map from scratch (get, set, remove), then extend it… →