23. Merge K Sorted Lists
hardAsked at AndurilMerge k sorted linked lists into one sorted list efficiently. Anduril asks this hard problem to test whether you can design an O(n log k) solution using a min-heap or divide-and-conquer — skills that translate directly to merging ordered event streams from multiple autonomous subsystems in real time.
By Alex Chen, Founder, InterviewChamp.AI · Last verified
Source citations
Public interview reports confirming this problem appears in Anduril loops.
- Glassdoor (2025-12)— Mentioned in Anduril senior SWE onsite reports as a hard question testing heap and divide-and-conquer knowledge.
- Blind (2025-10)— Anduril senior engineering threads cite Merge K Sorted Lists as a high-signal hard problem for systems-focused roles.
Problem
You are given an array of k linked lists lists, each linked list is sorted in ascending order. Merge all the linked lists into one sorted linked list and return it.
Constraints
k == lists.length0 <= k <= 10^40 <= lists[i].length <= 500−10^4 <= lists[i][j] <= 10^4lists[i] is sorted in ascending order.The sum of lists[i].length will not exceed 10^4.
Examples
Example 1
lists = [[1,4,5],[1,3,4],[2,6]][1,1,2,3,4,4,5,6]Explanation: All three lists merged in sorted order.
Example 2
lists = [][]Explanation: No lists to merge.
Approaches
1. Divide and conquer (pairwise merge)
Merge lists in pairs repeatedly until one list remains. Like merge sort: O(log k) rounds, each round touches all n elements.
- Time
- O(n log k)
- Space
- O(log k) call stack
function mergeKLists(lists) {
if (lists.length === 0) return null;
function mergeTwoLists(l1, l2) {
const dummy = { val: 0, next: null };
let tail = dummy;
while (l1 && l2) {
if (l1.val <= l2.val) { tail.next = l1; l1 = l1.next; }
else { tail.next = l2; l2 = l2.next; }
tail = tail.next;
}
tail.next = l1 || l2;
return dummy.next;
}
while (lists.length > 1) {
const merged = [];
for (let i = 0; i < lists.length; i += 2) {
merged.push(mergeTwoLists(lists[i], lists[i + 1] ?? null));
}
lists = merged;
}
return lists[0];
}Tradeoff: O(n log k) time. Avoids the overhead of a heap. The pairwise merge pattern is intuitive and maps directly to merge sort. This is the approach Anduril engineers often prefer — it's predictable and composable.
2. Min-heap (priority queue)
Initialize a min-heap with the head of each non-null list. Repeatedly extract the minimum, append it to the result, and insert the next node from that list.
- Time
- O(n log k)
- Space
- O(k)
// JS has no built-in heap; this shows the algorithm structure.
// In a real implementation, use a proper min-heap library.
function mergeKLists(lists) {
// Simulate with sorted structure insertion for illustration.
// Real approach: use a min-heap keyed on node.val.
const dummy = { val: 0, next: null };
let tail = dummy;
// Collect all nodes, sort by value — O(n log n) but shows the intent.
const nodes = [];
for (const head of lists) {
let curr = head;
while (curr) { nodes.push(curr); curr = curr.next; }
}
nodes.sort((a, b) => a.val - b.val);
for (const node of nodes) {
tail.next = node;
node.next = null;
tail = tail.next;
}
return dummy.next;
}Tradeoff: The O(n log n) sort simulation above is for illustration. A true min-heap gives O(n log k). Mention to the interviewer that you'd implement a proper heap in C++ using std::priority_queue or in Python using heapq. In a JS interview, describe the heap approach verbally and implement divide-and-conquer.
Anduril-specific tips
Anduril expects you to know both approaches and compare them. Start by ruling out the O(kn) sequential merge (merge list 1 into list 2, result into list 3, etc.) — this approach processes early elements O(k) times. Then present divide-and-conquer as your primary answer and describe the heap approach as an alternative that's better for k >> n. Mention that in C++, std::priority_queue makes the heap approach concise. The divide-and-conquer is safer in JS where heap isn't built in.
Common mistakes
- Sequential merge (fold from left) — O(kn) total because the first list's elements are merged k-1 times.
- Not handling null lists in the divide-and-conquer — when k is odd, the last pair has one null; pass null to mergeTwoLists explicitly.
- Forgetting to set node.next = null when re-linking in the collect-and-sort approach — creates cycles in the result.
- Confusing n (total elements) with k (number of lists) in the complexity analysis — O(n log k), not O(k log n).
Follow-up questions
An interviewer at Anduril may pivot to one of these next:
- How would you solve this if the k lists were arriving as real-time streams?
- What if the lists were doubly linked?
- Implement a generic merge for k sorted arrays (not linked lists) — can you achieve better cache performance?
Solve it now
Free. No sign-up. Python and JavaScript run instantly in your browser.
FAQ
Why is sequential merge O(kn) not O(n log k)?
Each of the k-1 merges processes all previously merged elements again. If each list has n/k elements, list 1 is merged k-1 times — total O(kn/k * k) = O(kn).
Why is divide-and-conquer O(n log k)?
There are log k rounds. In each round, every element is touched exactly once across all pairwise merges. Total = n * log k.
Which is faster in practice: heap or divide-and-conquer?
Divide-and-conquer has better cache behavior since it works on contiguous node chains. Heap incurs pointer-chasing overhead for each extraction. For most practical k values, divide-and-conquer is faster.
Practice these live with InterviewChamp.AI
Drill Merge K Sorted Lists and other Anduril interview questions under real-loop conditions with instant feedback on your reasoning, complexity claims, and code.
Practice these live with InterviewChamp.AI →