Merging sorted linked lists
Combine two sorted linked lists into one sorted list by repeatedly taking the smaller front node.
O(n + m)Space O(1)3 practice problemsStep-through pictureThe idea
You have two linked lists, each already sorted from smallest to largest. You want one sorted list with every node from both. Because each list is sorted, the smallest node overall must be at the front of one of them. Take the smaller of the two front nodes, attach it to the answer, and repeat.
The answer is built by relinking the existing nodes, not by copying values. A tail pointer marks the last node of the answer so far, and each chosen node is hooked on after it. A dummy node in front of the answer means the first node needs no special case.
When one list runs out, the other one is already sorted and every node in it is at least as big as everything taken so far, so you attach the whole rest in one step.
An everyday example
Merging two sorted stacks of exam papers
Two teachers each hand you a pile of exam papers sorted by student number, lowest on top. You want one sorted pile. You look at the top paper of each pile, put the lower number face down on your new pile, and repeat. When one pile is empty, you put the whole other pile on top at once, because it is already in order.
| In the example | In the code |
|---|---|
| The two sorted piles | lists a and b |
| The top paper of each pile | a.val and b.val |
| The top of your new pile | tail |
| Moving the lower paper over | tail.next = a; a = a.next |
| Dropping the leftover pile on at once | tail.next = a !== null ? a : b |
From the slow way to merging sorted linked lists
- Start with the obvious way: walk both lists, copy every value into one array, sort the array, and build a new list from it. That costs
O((n + m) log(n + m))time for the sort,O(n + m)extra memory, and new nodes, and it ignores the most useful fact we have: each list is already sorted. - Sorted means the front node of each list is the smallest node still in that list. So the smallest node left anywhere must be one of the two front nodes. One comparison,
a.val <= b.val, tells you which node comes next in the answer. No other node needs to be looked at. - To build the answer without copying, keep a
tailpointer at its last node and hook each chosen node on withtail.next, then movetailforward. Adummynode at the very start means the answer is never empty, so attaching the first node is no different from attaching any other. - When one list runs out, every node left in the other is at least as big as everything already taken, and it is already sorted. So one link,
tail.next = a !== null ? a : b, attaches all of it. That loop is merging sorted linked lists.
How it works
- Make a
dummynode and settail = dummy. The answer will hang offdummy.next. - While both
aandbhave nodes, comparea.valandb.val. - Attach the smaller node after
tailand move that list forward. On a tie, take fromaso equal values keep their order. - Move
tailto the node just attached. - When one list is empty, attach the other list's remaining nodes in one step.
- Return
dummy.next.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function mergeLists(a, b) {const dummy = new ListNode(0);let tail = dummy;while (a !== null && b !== null) {if (a.val <= b.val) {tail.next = a;a = a.next;} else {tail.next = b;b = b.next;}tail = tail.next;}tail.next = a !== null ? a : b;return dummy.next;}
How fast it is
| Time | O(n + m) |
|---|---|
| Extra memory | O(1) |
Each loop moves one node into the answer, and the leftover list is attached in one step. The nodes are relinked, not copied.
- Best case
O(min(n, m))Every node of the shorter list is smaller than the first node of the longer one, like
1 -> 2 -> 3and10 -> 20 -> 30 -> 40. The loop runs 3 times and the rest is attached with one link. - Average case
O(n + m)Values from the two lists mix, so most nodes are taken one by one through a comparison.
- Worst case
O(n + m)The values alternate perfectly, like
1 -> 3 -> 5and2 -> 4 -> 6. The loop runsn + m - 1 = 5times and only the last node is attached as a leftover.
Common mistakes
- Forgetting to attach the leftover list after the loop, which drops the largest values.
- Forgetting
tail = tail.next, so every new node overwrites the same link and the answer keeps only the last one. - Making new nodes for every value when the task says to reuse the existing ones. It works but uses
O(n + m)extra memory. - Merging
klists one at a time into one growing list, which makes the early nodes get walked over again and again.
When to use it
Look for these clues in a problem:
- Two or more inputs are linked lists that are already sorted.
- The answer must be sorted and contain every node from the inputs.
- You are sorting a linked list with merge sort and need the merge step.
- The task asks to relink nodes rather than create new ones.
Check yourself
Answer each one in your head first, then open it.
Why is it safe to attach the whole leftover list with one link?
It is already sorted, and every node in it is at least as big as the last node taken. So no node in it needs to be compared with anything.
What happens if you forget tail = tail.next?
Every chosen node is hooked onto the same tail, so each attach overwrites the last one. The answer ends up holding only the last node chosen plus the leftover list.
At most how many comparisons does merging lists of 3 and 5 nodes take?
7, which is 3 + 5 - 1. The loop stops when one list is empty, and at that point the other still has at least one node.
Why use <= instead of <?
On ties <= takes a's node first, so equal values keep the order of the two inputs. That makes the merge stable.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 3 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingGo further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
The loop keeps one rule, true at the top of every loop: the chain from dummy.next to tail is sorted, it holds exactly the nodes taken so far, and every node still in a or b is at least tail.val. At the start the chain is empty, so the rule holds.
Each step keeps the rule. Because both lists are sorted, a's front is the smallest node left in a and b's front is the smallest left in b. The smaller of the two is the smallest node left anywhere, so hooking it after tail keeps the chain sorted, and everything still waiting is at least as big as it.
The loop ends because each pass moves a or b one node forward, so after at most n + m - 1 passes one of them is null. Then the other list is sorted and, by the rule, every node in it is at least tail.val, so attaching it whole keeps the answer sorted and complete.
Ties go to a because of <=. So if two nodes have equal values, the one from a comes first, and nodes from the same list never change their order. A merge with this property is called stable, which matters when the nodes carry more than a number, such as messages sorted by time.
Going deeper
3 more ideas
Why the smaller front node is always right
Both lists are sorted, so a's front node is the smallest node left in a, and b's front node is the smallest left in b. The smaller of the two is therefore the smallest node left anywhere, which is exactly the node that belongs next in the answer.
Cost
Each loop attaches one node, so merging lists of lengths n and m takes O(n + m) time. No new nodes are made apart from dummy, so the extra space is O(1).
Merging more than two lists
To merge k sorted lists, merge them in pairs: list 1 with 2, 3 with 4, and so on, then merge the results in pairs again. Each round halves the number of lists, so the total work is O(N log k) for N nodes. Merging them one by one into a growing answer is slower, O(N k). A heap of the k front nodes is the other common way.
Work it by hand
Every step in a table, with why
Merge two four-node lists that share the value 5, so the trace shows how ties are broken and how the leftover list is attached.
a = 2 -> 5 -> 5 -> 9, b = 1 -> 5 -> 12 -> 14
| Step | a | b | a.val <= b.val | Taken | Answer so far | Why |
|---|---|---|---|---|---|---|
| 1 | 2 | 1 | false | 1 from b | 1 | 1 is smaller, so it is hooked onto dummy and b moves to its 5. |
| 2 | 2 | 5 | true | 2 from a | 1 -> 2 | 2 is smaller, so a gives up its front node and moves to its first 5. |
| 3 | 5 | 5 | true | 5 from a | 1 -> 2 -> 5 | A tie. <= takes from a, so equal values keep a's copies before b's. |
| 4 | 5 | 5 | true | 5 from a | 1 -> 2 -> 5 -> 5 | Another tie with b's 5. a's second 5 goes first for the same reason. |
| 5 | 9 | 5 | false | 5 from b | 1 -> 2 -> 5 -> 5 -> 5 | Now b's 5 is smaller than 9, so it is taken. |
| 6 | 9 | 12 | true | 9 from a | 1 -> 2 -> 5 -> 5 -> 5 -> 9 | 9 is taken and a becomes null, so the loop condition fails. |
| 7 | null | 12 | - | rest of b | ... -> 9 -> 12 -> 14 | a is empty, so tail.next = b attaches 12 -> 14 in one step without comparing them. |
It returns 1 -> 2 -> 5 -> 5 -> 5 -> 9 -> 12 -> 14: all 8 nodes after 6 comparisons. The last two nodes were attached with a single link.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Merging sorted linked lists | O(n + m) | O(1) | Both inputs are already sorted and you can relink their nodes. |
| Collect all values, sort, rebuild | O(n log n) | O(n) | The inputs aren't sorted, or you need brand-new nodes and don't care about speed. |
| Recursive merge | O(n + m) | O(n + m) | The lists are short and you prefer the shortest code. |
Extra memory: O(1): one dummy node and the tail pointer. The existing nodes are relinked, not copied. A recursive merge uses O(n + m) stack space instead.
How the steps grow
- Merging sorted linked lists, Recursive merge
O(n + m) - Collect all values, sort, rebuild
O(n log n)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Merging sorted linked lists | 20 | 2,000 | 2 million |
| Collect all values, sort, rebuild | 33 | 9,966 | 20 million |
| Recursive merge | 20 | 2,000 | 2 million |
Inputs that need care
5 tricky inputs
- One list is empty:
a = null - The loop never runs, and
tail.next = battaches all ofb. The function returnsbitself. - Both lists are empty
- The loop never runs,
tail.nextis set tonull, and the function returnsnull. - Equal values in both lists
<=takesa's node first. Use<only if the task wantsb's copies first; either way the result is sorted.- Very different lengths: one node against 1,000 nodes
- At most one comparison per node of the short list, then one link attaches the rest of the long list. The length difference costs nothing extra.
- An input list that isn't sorted, like
a = 4 -> 1 - The code doesn't check. The result contains every node but may not be sorted: with
b = 2 -> 3, it returns2 -> 3 -> 4 -> 1. Merge only sorted input, or sort first.
Variations and follow-ups
Twists interviewers add
Merge k sorted lists
Merge the lists in pairs, then merge the results in pairs, until one list is left. Each round touches every node once and halves the number of lists, so it takes O(N log k) for N nodes in total. A heap of the k front nodes gives the same time and picks the smallest front each step.
Merge sort on a linked list
Split the list at its middle with fast and slow pointers, sort each half the same way, and merge the two sorted halves with this loop. It sorts a linked list in O(n log n) time without copying it into an array.
Recursive merge
Return the smaller front node after setting its next to the merge of the rest. It needs no dummy or tail, but it uses one stack frame per node taken, which is O(n + m) space.
Merge and drop duplicates
Before hooking a node on, check whether its value equals tail.val. If it does, skip it instead of attaching it. The answer then holds each value once, still in sorted order.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "two sorted lists", "combine into one sorted list", or a merge sort on a linked list. Also from "merge
ksorted lists", which builds on this loop. - Explain the one-line reason out loud: "the smallest node left must be one of the two fronts, because both lists are sorted". Then mention the
dummynode so the first attach is not special. - Test by hand: one empty list, both empty, and lists with equal values. Check that the leftover list is attached and that
tailmoves every time. - Expect the follow-up "what about
klists?". Answer with pairwise merging or a heap forO(N log k), and explain why merging one list at a time into a growing answer isO(N k).
Where frontend code uses it
The same idea in real UI code
- A chat or activity feed that loads older messages from two sources (for example, local cache and server) merges two time-sorted lists into one timeline.
- Merge sort is a common stable sort, and
Array.prototype.sortmust be stable in modern JavaScript; V8 uses TimSort, which is built on merging sorted runs. - Combining sorted change lists, such as edits from two collaborators ordered by timestamp, uses the same take-the-smaller-front step.