Merge Two Sorted Lists: The Fundamental Merge Operation
Merge Two Sorted Lists is LeetCode problem 21 and one of the most important easy problems to master. It is the building block that powers merge sort, merge k sorted lists, and a whole family of problems that combine sorted sequences. If you can write this from memory, you unlock a lot of harder problems for free.
The problem
You are given the heads of two sorted linked lists list1 and list2. Merge the two lists into one sorted list by splicing together the nodes of the two input lists. Return the head of the merged linked list.
Example: list1 = [1, 2, 4], list2 = [1, 3, 4]. The merged result is [1, 1, 2, 3, 4, 4].
The approach: dummy head and two pointers
The idea is simple. You maintain a pointer into each list and a pointer to the tail of the result. At each step, compare the two heads. Whichever is smaller (or equal), attach it to the result and advance that pointer. When one list runs out, attach the remainder of the other list.
There is one small trick that makes the code clean: use a dummy head node. Without it, you need special logic to handle the very first node (since there is no "previous node" to attach to). The dummy node acts as a placeholder. You build the result after it, then return dummy.next at the end.
Three pointers:
p1walks throughlist1p2walks throughlist2currtracks the tail of the merged result
At each step:
- If
p1.valis less than or equal top2.val, setcurr.next = p1and advancep1. - Otherwise, set
curr.next = p2and advancep2. - Advance
currtocurr.next. - When either list is exhausted, attach the remaining nodes from the other list.
Visual walkthrough
Let's trace through list1 = [1, 2, 4] and list2 = [1, 3, 4]. Watch the two pointers (p1 in amber, p2 in green) as they advance through each list.
Step 1: Compare p1=1 vs p2=1. Pick p1 (equal, take from list1). Advance p1.
Both heads are 1. We pick from list1. dummy.next now points to node 1 from list1.
Step 2: Compare p1=2 vs p2=1. Pick p2 (1 is smaller). Advance p2.
list2's head (1) is smaller than list1's head (2). Append 1 from list2.
Step 3: Compare p1=2 vs p2=3. Pick p1 (2 is smaller). Advance p1.
list1's head (2) is smaller than list2's head (3). Append 2 from list1.
Step 4: Compare p1=4 vs p2=3. Pick p2 (3 is smaller). Advance p2.
list2's head (3) is smaller than list1's head (4). Append 3 from list2.
Step 5: Compare p1=4 vs p2=4. Pick p1 (equal, take from list1). Advance p1.
Both heads are 4. We pick from list1. p1 is now exhausted.
Step 6: p1 is exhausted. Append the rest of list2. Done!
list1 is empty. Attach the remaining list2 node (4) to the result. Final: [1,1,2,3,4,4].
Notice how in step 6, when p1 is exhausted, you just point curr.next to the remaining nodes of list2. No copying needed. The remaining nodes are already sorted and already linked together.
The code
Here is the Python solution:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def merge_two_lists(list1, list2):
dummy = ListNode(0)
curr = dummy
while list1 and list2:
if list1.val <= list2.val:
curr.next = list1
list1 = list1.next
else:
curr.next = list2
list2 = list2.next
curr = curr.next
curr.next = list1 if list1 else list2
return dummy.next
The key details:
while list1 and list2runs as long as both lists have nodes remaining. The moment one runs out, the loop exits.list1.val <= list2.valuses<=to maintain stability (nodes fromlist1come first when values are equal), though either choice produces a valid sorted result.curr.next = list1 if list1 else list2handles the cleanup. Whichever list still has nodes gets attached directly. No loop needed since those nodes are already linked.dummy.nextskips past the placeholder and returns the real head of the merged list.
The dummy head technique eliminates all edge-case handling for the first node. Without it, you would need an if result is None check inside the loop. The dummy node costs nothing (one extra allocation) and makes the code much cleaner.
Complexity analysis
| Metric | Value | Reasoning |
|---|---|---|
| Time | O(n + m) | Each node is visited exactly once. n and m are the lengths of the two lists. |
| Space | O(1) | Only a few pointers are used. No new nodes are created. The dummy node is constant overhead. |
This is optimal. You must examine every node at least once to determine its position, so O(n + m) is a lower bound. And since you splice existing nodes rather than creating new ones, no extra space is needed beyond the pointers.
The building blocks
This problem is built from two core building blocks that appear in dozens of other problems.
Two-pointer merge
You have two sorted sequences and a pointer into each one. At each step, compare the current elements, pick the one that belongs next, and advance that pointer. This is the merge step from merge sort, and it is one of the most reusable patterns in algorithm design.
This same building block shows up in:
- Merge Sorted Array (LeetCode 88) applies the same logic to arrays, filling from the back for in-place operation.
- Merge K Sorted Lists (LeetCode 23) extends it using a heap or divide-and-conquer to handle k inputs.
- Intersection of Two Arrays II (LeetCode 350) uses the merge walk but only keeps matching elements.
Dummy head technique
When building a linked list node by node, create a dummy sentinel node at the start. Append new nodes after it. Return dummy.next when done. This avoids special-casing the first insertion and eliminates null-check bugs.
You will see the dummy head in nearly every linked list construction problem: merge lists, partition lists, add two numbers, and more. Once this technique is automatic, you stop thinking about edge cases at the head of the list entirely.
Edge cases to watch for
- One list is empty. If
list1isNone, the while loop never runs and you returnlist2directly (and vice versa). Thecurr.next = list1 if list1 else list2line handles this. - Both lists are empty. Both are
None, the loop never runs,curr.nextis set toNone, anddummy.nextreturnsNone. - One element each.
list1 = [1],list2 = [2]. One comparison, one attachment, then the cleanup line attaches the remaining node. - All elements from one list are smaller. For example,
list1 = [1, 2, 3],list2 = [7, 8, 9]. The loop picks all oflist1first, then attaches all oflist2in the cleanup. - Duplicate values across lists.
list1 = [1, 1],list2 = [1, 1]. The<=comparison handles this cleanly, alternating picks between the two lists.
From understanding to recall
This is one of those problems where the solution is short enough to feel trivial once you see it. That is exactly what makes it dangerous. You read it, understand it, and assume you will remember it. But under interview pressure, the details slip. Do you use <= or <? Where does the dummy node go? What happens when one list runs out?
The only way to make these details automatic is repetition. Not re-reading the solution, but typing it from scratch. Spaced repetition schedules the practice at exactly the right intervals so you retain it with minimal time invested. After a few reps, the dummy head and the two-pointer merge become muscle memory.
Related posts
- Merge Sorted Array - The same two-pointer merge applied to arrays, with the backwards fill trick for in-place operation
- Merge K Sorted Lists - Scaling the merge to k lists using a min heap or divide and conquer
Visual Learner? Explore how linked list operations work with our Linked List Interactive Visualization.