Merging & splitting lists
In one line
Merge two sorted lists by repeatedly attaching the smaller head to a growing tail, split a list at its middle with fast and slow pointers, and combine the two to merge sort a linked list in O(n log n).
What it is
Merging takes two sorted lists and produces one sorted list by relinking the existing nodes: compare the two heads, attach the smaller to the result, advance in that list, repeat, and attach whatever remains at the end. No new nodes are created.
Splitting cuts a list into parts: at the middle (with fast and slow pointers), by a condition (odd and even positions, values below and above x), or into k roughly equal parts. Merge sort on a linked list is split plus merge applied recursively, and merging k sorted lists uses a min-heap or pairwise merging.
Why interviewers ask
Merge Two Sorted Lists, Sort List and Merge k Sorted Lists are very common. They test clean pointer handling with a dummy node, correct handling of leftovers, and knowing why merge sort is the natural sort for linked lists (no random access needed). Merge k lists also brings in heaps and complexity analysis with N total nodes and k lists.
How it works
- 1Merge two: create a dummy node and a tail pointer at it. While both lists have nodes, attach the smaller head to tail.next, advance that list and advance tail.
- 2When one list is empty, attach the other list in one step: tail.next = a or b. The remaining nodes are already sorted.
- 3Return dummy.next. Time O(n + m), extra space O(1).
- 4Split at the middle: run slow and fast pointers (start fast at head.next so slow ends at the end of the first half), then set second = slow.next and slow.next = None to cut the link.
- 5Merge sort: if the list has 0 or 1 nodes return it; otherwise split, sort both halves recursively and merge them. Time O(n log n), recursion depth O(log n).
- 6Merge k lists: push each list's head into a min-heap keyed by value; repeatedly pop the smallest, attach it, and push its next node. With N total nodes this is O(N log k).
Worked example · Merge 1 -> 4 -> 6 with 2 -> 3 -> 7
- Start: result is dummy, tail = dummy, a = 1, b = 2.
- 1 <= 2: attach 1, a = 4. Result: 1. Then 4 > 2: attach 2, b = 3. Result: 1 -> 2.
- 4 > 3: attach 3, b = 7. Result: 1 -> 2 -> 3. Then 4 <= 7: attach 4, a = 6. Result: 1 -> 2 -> 3 -> 4.
- 6 <= 7: attach 6, a = None. Result: 1 -> 2 -> 3 -> 4 -> 6.
- a is empty, so attach the rest of b in one step: 7. Final: 1 -> 2 -> 3 -> 4 -> 6 -> 7. Five comparisons, zero new nodes (other than the dummy).
Picture it
Two sorted piles of exam papers, each in alphabetical order. You look at the top paper of each pile, take whichever name comes first and put it on the output pile, and repeat. When one pile runs out, you drop the whole other pile on top: it is already in order.
Reference code
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def merge_two(a, b):
dummy = tail = ListNode()
while a and b:
if a.val <= b.val: # <= keeps equal values stable
tail.next, a = a, a.next
else:
tail.next, b = b, b.next
tail = tail.next
tail.next = a or b # attach whatever is left
return dummy.next
def sort_list(head):
"""Merge sort a linked list: O(n log n) time, no array copy."""
if not head or not head.next:
return head
slow, fast = head, head.next # slow stops at the end of the first half
while fast and fast.next:
slow, fast = slow.next, fast.next.next
second, slow.next = slow.next, None # split into two lists
return merge_two(sort_list(head), sort_list(second))
merge_two relinks existing nodes behind a dummy node and attaches the leftover list in one step; using <= keeps the merge stable. sort_list starts fast at head.next so that a two-node list splits into one and one, avoiding infinite recursion. merge_two is O(n + m) time and O(1) space; sort_list is O(n log n) time with O(log n) recursion depth.
Key patterns
Merge two sorted lists
When: Combine two sorted lists into one
How: Dummy + tail; attach the smaller head; attach leftovers at the end.
Merge k sorted lists
When: k sorted lists with N nodes in total
How: Min-heap of current heads, O(N log k); or merge in pairs like a tournament, also O(N log k).
Split at the middle
When: Merge sort, palindrome, reorder list
How: Fast and slow pointers, then cut with slow.next = None.
Split by condition
When: Partition list, odd-even list
How: Build two lists with two dummy heads, then join them; set the last tail.next = None.
Merge sort a linked list
When: Sort a list in O(n log n)
How: Split at the middle, sort both halves recursively, merge. Bottom-up merging gives O(1) extra space.
Real-world use
Linux kernel list_sort
The Linux kernel sorts its linked lists (struct list_head) with list_sort, a bottom-up merge sort, because merge sort needs no random access and is stable.
LSM-tree compaction
RocksDB, LevelDB and Cassandra keep data in sorted runs (SSTables) and periodically merge several sorted runs into one, using the same pick-the-smallest-head idea as merging k sorted lists.
Search engine posting lists
Lucene and Elasticsearch store, for each term, a sorted list of document ids; answering 'term1 OR term2' merges the sorted lists, and 'AND' walks them in step to find common ids.
External sorting
Databases sort data larger than memory by sorting chunks that fit in RAM, writing them as sorted runs, then k-way merging the runs from disk with a heap.
In a project
A notification service gathers events for each user from 20 microservices. Each service returns its events already sorted by time, and the feed must show them merged in time order. The current code concatenates all lists and sorts them, which is slow for heavy users with 50,000 events.
- Treat each service's response as a sorted list and do a k-way merge with a min-heap of (timestamp, service, position).
- Pop the earliest event, append it to the feed, and push the next event from the same service.
- Stop after the first page (for example 50 events) instead of merging everything, since the heap produces events in order.
- Break ties by a stable key (service id) so the feed order is deterministic, and test with empty and single-item lists.
Impact: The first page costs O(k + p log k), which is about 50 heap operations instead of sorting 50,000 events, so feed latency drops sharply for heavy users and CPU use on the service falls.
Trade-offs
Use it when
- Inputs are already sorted (merge instead of re-sorting)
- Sorting a linked list: merge sort needs no random access
- Combining many sorted streams lazily, page by page
Avoid it when
- The data is in an array and small: built-in array sorts are faster in practice
- Inputs are not sorted: merging gives a wrong result
- Quick sort on linked lists: picking pivots and partitioning is awkward and the worst case is O(n^2)
Cost: Merge two: O(n + m) time, O(1) space. Merge sort: O(n log n) time, O(log n) stack (O(1) bottom-up). Merge k: O(N log k) time, O(k) heap.
Common mistakes
- Forgetting to attach the leftover list after the loop, losing the tail of one input.
- Splitting without cutting slow.next = None, so the first half still runs into the second.
- Using the 'second middle' split in merge sort, so a two-node list never shrinks and the recursion never ends.
- Merging k lists one after another into a growing result, which is O(N x k) instead of O(N log k).
- In Python, pushing nodes into heapq without a tie-breaker, which fails when two values are equal because nodes cannot be compared.
Interview questions
Quick revision
- Merge: dummy + tail, attach smaller, tail.next = a or b at the end.
- Merge two: O(n + m) time, O(1) space.
- Split: fast = head.next for first middle; cut with slow.next = None.
- Merge sort list: O(n log n), stable, no random access needed.
- Merge k: heap of heads, O(N log k); tie-break with an index.
- Sequential merging of k lists is O(N x k): avoid.
- Split by condition: two dummy heads, terminate the last list.
Check yourself
1. What is the time to merge k sorted lists with N total nodes using a min-heap?
2. After the main merge loop ends because list a is empty, what should you do?
3. Why is merge sort preferred over quick sort for linked lists?
4. When splitting a list for merge sort, what happens if you forget slow.next = None?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.