Skip to content
DSA Patterns

Learn

K-way Merge

Merging k sorted sequences with a min-heap of k candidates, why it beats merging pairwise, and the sorted-matrix and smallest-range variants.

3 min readUpdated 2 Sept 2026

#Heap#Linked List#Sorting#Merge

When several already-sorted sequences must become one, the naive answers — concatenate and sort, or merge two at a time — both waste the sortedness you were handed. The pattern is a min-heap holding exactly one candidate from each sequence.

The idea

The smallest unmerged element must be at the front of one of the k lists. So keep those k front elements in a min-heap: the root is the global minimum, and when you pop it you push its successor from the same list to take its place.

import heapq

def merge_k_lists(lists):
    heap = []
    for i, node in enumerate(lists):
        if node:
            # i breaks ties: ListNode has no ordering, and Python's heap
            # falls through to comparing the next tuple element on equal keys.
            heapq.heappush(heap, (node.val, i, node))

    dummy = tail = ListNode(0)
    while heap:
        _, i, node = heapq.heappop(heap)
        tail.next = node
        tail = node
        if node.next:
            heapq.heappush(heap, (node.next.val, i, node.next))

    return dummy.next

That tie-breaker index is not a detail — without it, two equal values make Python compare the ListNodes themselves and raise a TypeError. Interviewers who have seen the problem before watch for it.

Why not merge pairwise

Merging list 1 into list 2, then that into list 3, and so on, re-walks the accumulated result every time: O(k × N) where N is the total number of nodes. The heap touches each node once at O(log k), giving O(N log k).

Divide-and-conquer merging — pair up the lists, merge each pair, repeat — reaches the same O(N log k) without a heap, and is worth mentioning as the alternative:

def merge_k_lists_dc(lists):
    if not lists:
        return None
    while len(lists) > 1:
        merged = []
        for i in range(0, len(lists), 2):
            second = lists[i + 1] if i + 1 < len(lists) else None
            merged.append(merge_two(lists[i], second))
        lists = merged
    return lists[0]

Each round halves the number of lists, and each round touches every node once: log k rounds × N work.

Sorted matrix as k lists

Kth Smallest Element in a Sorted Matrix is k-way merge with the rows as the lists: seed

the heap with the first element of each row and pop k times.

def kth_smallest(matrix, k):
    n = len(matrix)
    heap = [(matrix[r][0], r, 0) for r in range(min(n, k))]
    heapq.heapify(heap)

    for _ in range(k - 1):
        _, r, c = heapq.heappop(heap)
        if c + 1 < n:
            heapq.heappush(heap, (matrix[r][c + 1], r, c + 1))

    return heap[0][0]

O(k log n). There is also a binary search on the answer solution — binary search the value range and count how many cells are ≤ mid — which is O(n log(max − min)) and wins when k is large. Knowing both, and when each is better, is the full answer.

Smallest Range Covering Elements from K Lists

The variant that shows the pattern is about more than merging. Keep one element from each list in the heap, and also track the maximum currently held. The heap root and that maximum define a range that covers all k lists by construction; record it, then advance the minimum's list and repeat. Every candidate range is examined without ever materialising the merge.

Complexity

O(N log k) time for the merge, O(k) space for the heap — the space is the reason this is the external-sorting algorithm too, where the k sequences are files far larger than memory. Divide-and-conquer is the same time bound with O(log k) recursion space instead.

Mistakes that cost the round

  • No tie-breaker in the heap tuple, so equal values make the comparison fall through

to an object with no ordering.

  • Pushing whole lists into the heap instead of one candidate each — that is

"concatenate and sort" wearing a heap costume, at O(N log N).

  • Merging pairwise in a loop, which is O(k × N).
  • Forgetting to push the successor after a pop, which silently truncates a list.
  • Seeding more heap entries than needed in the matrix variant — min(n, k) rows is

enough, since the answer cannot come from row k + 1.

What to drill

  1. Merge Two Sorted Lists — the two-pointer base case.
  2. Merge k Sorted Lists — the heap, and the divide-and-conquer alternative.
  3. Kth Smallest Element in a Sorted Matrix — rows as lists, plus the binary-search

solution.

  1. Smallest Range Covering Elements from K Lists — the heap with a tracked maximum.

All on the 22 DSA Patterns sheet.

Frequently asked

How do you merge k sorted lists efficiently?

Push the head of each list into a min-heap, then repeatedly pop the smallest and push that node's successor from the same list. The heap holds at most k entries, so each of the N nodes costs O(log k) — O(N log k) overall, against O(k × N) for merging the lists pairwise.

Why does my heap raise a TypeError on ListNodes?

Because two entries with equal values make Python compare the next element of the tuple, which is the node itself, and ListNode defines no ordering. Insert a unique tie-breaker — the list's index or a counter — between the value and the node.

Is a heap or divide-and-conquer better for merging k lists?

Both are O(N log k). The heap uses O(k) space and works on streams where the lists arrive incrementally; divide-and-conquer avoids the heap entirely and is often slightly faster in practice on in-memory lists. Either is a full-marks answer if you can state the complexity.

Related