Sort a linked list is the classic place where merge sort shines. You cannot index into a linked list, so the array-friendly sorts (quicksort with random access, heapsort) are awkward — but merge sort only ever walks forward and splices pointers, which is exactly what a linked list is good at.
Problem. Given the head of a singly linked list, return the head of the list sorted in
ascending order.
Example: 4 -> 2 -> 1 -> 3 becomes 1 -> 2 -> 3 -> 4. Aim for O(n log n) time.
The slow way first
The obvious idea: collect every value into an array, sort the array, then overwrite the node values. That works and is O(n log n), but it needs O(n) extra space for the array and quietly ignores the structure of the problem.
The question to ask: what sort splices naturally onto a linked list? Merge sort. It never needs random access — it only splits a list in half and merges two sorted lists by re-pointing next links. Both operations are pure pointer walks, so merge sort fits a linked list perfectly and runs in O(n log n).
The idea: split, sort each half, merge
Merge sort is three moves, applied recursively:
- Split the list into two halves. Find the middle with the slow/fast trick:
fastmoves two nodes for every oneslowmoves, so whenfastruns off the end,slowsits on the last node of the first half. Cut the link there. - Recurse on each half until a half has 0 or 1 nodes — that is the base case, a list that is already sorted.
- Merge the two sorted halves into one sorted list by repeatedly taking the smaller head and re-pointing
next.
The key insight: the recursion does the hard part for free. By the time we call merge, both halves are already sorted, so merging is a simple two-pointer walk.
Walk through it
Step through the animation. First the slow and fast pointers find the middle of 4 -> 2 -> 1 -> 3, and we cut the list into 4 -> 2 and 1 -> 3. Each half sorts itself: the left becomes 2 -> 4, the right stays 1 -> 3. Then the merge takes the smaller head each time — 1, then 2, then 3, then 4 — threading the nodes back together as 1 -> 2 -> 3 -> 4.
Pseudocode
sort(head):
if list has 0 or 1 node: return head # already sorted (base case)
mid = split list in half # slow/fast, then cut
left = sort(first half) # recurse
right = sort(second half) # recurse
return merge(left, right) # combine two sorted lists
get_mid(head):
slow = head, fast = head.next
while fast and fast.next: # fast moves twice as fast
slow = slow.next
fast = fast.next.next
mid = slow.next # second half starts here
slow.next = None # cut the list in two
return midThe Python solution
def sort_list(head):
if not head or not head.next:
return head
mid = get_mid(head)
left = sort_list(head)
right = sort_list(mid)
return merge(left, right)
def get_mid(head):
slow, fast = head, head.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
mid = slow.next
slow.next = None
return midsort_listis the recursion. If the list is empty or a single node it is already sorted, so we return it unchanged — that is the base case.mid = get_mid(head)finds the start of the second half and cuts the list, soheadnow refers only to the first half.leftandrightare the two halves sorted by the recursive calls;mergecombines them. (A smallmergehelper walks both sorted lists, always splicing the smaller head onto the result.)- In
get_mid,faststarts one ahead and advances two nodes per loop. Whenfastfalls off the end,slowis on the last node of the first half. slow.next = Nonesevers the link between the halves so each half is its own independent list.
Complexity
| Case | Time | Notes |
|---|---|---|
| Collect to array + sort | O(n log n) (moderate) | but O(n) extra space |
| Merge sort (this solution) | O(n log n) (moderate) | log n levels, O(n) merge per level |
O(log n) (fast)Each level of recursion does O(n) total work merging, and there are O(log n) levels of splitting, giving O(n log n). The only extra space is the recursion stack, O(log n) — we never copy the values into an array, we just re-point existing nodes.
When this pattern shows up
The slow/fast pointer trick is worth memorizing on its own. It finds the middle of a linked list in one pass, detects cycles (Floyd), and finds the start of a cycle — all by moving one pointer twice as fast as the other. Reach for it any time you need the middle of a list without knowing its length.
When you cut the list, you must set slow.next = None. If you forget, the first half still points
into the second half, the two halves are not actually separated, and the recursion never bottoms out —
you get infinite recursion or a corrupted list.
Practice
For the list 4 -> 2 -> 1 -> 3, slow starts at 4 and fast starts at 2. After the loop ends, which node does slow point to, and where do we cut?
1. Why is merge sort preferred over quicksort for a linked list?
2. After get_mid runs on 4 -> 2 -> 1 -> 3, what are the two halves?
3. What is the base case of the recursion?
4. What is the time complexity of this solution?