Heaps · Heap Fundamentals

K-Way Merge Heap

Merging several already-sorted sequences by keeping one "current smallest candidate" per sequence in a heap, always emitting the global smallest and refilling from whichever sequence it came from.

  • 5 min read
  • 8 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: the smallest range that touches every list
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

k sorted sequences - log files sorted by time, sorted runs of an external sort, sorted results from k database shards - must become one sorted sequence. Concatenating and sorting ignores the order you already have and needs everything in memory at once. Only the front of each sequence can ever be next.

This skill covers the k-way merge: a heap holding one candidate per sequence, each tagged with where it came from, so N values merge in O(N log k) time with O(k) memory. The same frontier also finds the smallest range that touches every list.

We'll go from simple to more technical:

  1. The pattern: seed the heap with each sequence's first value, pop the minimum, push its successor.
  2. A second flavor: the smallest range covering k lists, found by advancing the minimum's list.
  3. Where k-way merges run (external sorts, LSM-tree compaction, sharded queries).
  4. What breaks it: empty sequences, loading everything, lost origins, overflowing comparators.

The approach

Java

int[] mergeKArrays(int[][] arrays) {
    PriorityQueue<int[]> heap = new PriorityQueue<>((x, y) -> Integer.compare(x[0], y[0]));  // {value, array, index}
    int total = 0;
    for (int i = 0; i < arrays.length; i++) {
        total += arrays[i].length;
        if (arrays[i].length > 0) heap.offer(new int[]{arrays[i][0], i, 0});   // skip empty arrays
    }
    int[] out = new int[total];
    int pos = 0;
    while (!heap.isEmpty()) {
        int[] top = heap.poll();
        out[pos++] = top[0];
        int arr = top[1], next = top[2] + 1;
        if (next < arrays[arr].length) heap.offer(new int[]{arrays[arr][next], arr, next});
    }
    return out;
}

Python

import heapq


def merge_k_arrays(arrays: list[list[int]]) -> list[int]:
    heap = [(arr[0], i, 0) for i, arr in enumerate(arrays) if arr]   # (value, array, index); skip empty arrays
    heapq.heapify(heap)
    out = []
    while heap:
        value, arr, idx = heapq.heappop(heap)
        out.append(value)
        if idx + 1 < len(arrays[arr]):
            heapq.heappush(heap, (arrays[arr][idx + 1], arr, idx + 1))
    return out

# heapq.merge(*arrays) is the same merge as a lazy iterator

The heap holds at most one entry per sequence - the smallest value not yet emitted from each - so the overall minimum is always at the root. Each entry carries its origin, which says where the replacement comes from. N values cost O(N log k) time and O(k) heap memory. Empty sequences must be skipped when seeding, and the comparator uses Integer.compare: x[0] - y[0] overflows once values span more than about 2^31. In Python, tuple entries (value, array, index) compare without any comparator to get wrong - and heapq.merge(*arrays) is this whole pattern as a lazy iterator.

A second flavor: the smallest range that touches every list

With the same one-candidate-per-list heap, you can find the smallest range [a, b] containing at least one value from each of k sorted lists. The heap's entries always cover every list, so [heap minimum, current maximum] is a valid range; to find a narrower one you must raise the minimum, so advance the list that holds it:

Java

while (true) {
    int[] top = heap.poll();                                   // current minimum, with its list and index
    if (max - top[0] < bestHi - bestLo) { bestLo = top[0]; bestHi = max; }   // strictly narrower
    if (top[2] + 1 == lists.get(top[1]).size()) break;        // that list is used up: no range can cover it now
    int next = lists.get(top[1]).get(top[2] + 1);
    heap.offer(new int[]{next, top[1], top[2] + 1});
    max = Math.max(max, next);
}

Python

while True:
    value, i, j = heapq.heappop(heap)                        # current minimum, with its list and index
    if max_val - value < best_hi - best_lo:                  # strictly narrower
        best_lo, best_hi = value, max_val
    if j + 1 == len(lists[i]):
        break                                                # that list is used up: no range can cover it now
    nxt = lists[i][j + 1]
    heapq.heappush(heap, (nxt, i, j + 1))
    max_val = max(max_val, nxt)

Only the minimum can be improved: every range starting at it must reach max anyway. When the list holding the minimum runs out, stop - any later range would have to skip that list. Updating only on a strictly narrower range keeps the smallest start among ties, because minima are visited in increasing order.

Where you'll see it

External merge sort (databases sorting more data than fits in memory, sort on huge files) sorts chunks, writes them as runs, and k-way merges the runs. LSM-tree databases (LevelDB, RocksDB, Cassandra) merge sorted SSTables during compaction the same way, and a distributed query merges sorted shard results before returning the top rows.

Which tool for which job?

You needUse
Merge k sorted sequences, streaminga heap of one entry per sequence
The same in Pythonheapq.merge(*seqs), a lazy iterator
Fewest comparisons per value, large ka loser (tournament) tree
Merge with no heap at allpairs in rounds, as in merge sort
The smallest range touching every listthe same heap, plus the current maximum
Data far bigger than memoryexternal sort: sorted runs, then k-way merge passes

Common mistakes

MistakeWhat happensDo this instead
Seeding from empty sequencesan index errorskip them when seeding
Pushing every value into the heapO(N) memory; the O(k) advantage is goneone entry per sequence
Entries without their originno way to fetch the next valuestore (value, sequence, index)
x[0] - y[0] as the comparatorwrong order for values far apartInteger.compare
Merging lists one at a timeO(N k): 32.5 comparisons a value for 64 runsa heap, or pairs in rounds

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why does the heap never hold more than k entries?

Show answer

Each sequence has at most one entry in it - the smallest value of that sequence not yet emitted. A sequence's next value is pushed only when its current one is popped.

2. What does each heap entry need besides the value, and why?

Show answer

Which sequence it came from and its index there. After emitting it, you need to know where the replacement is: the next index in the same sequence.

3. In the smallest-range search, why advance the list holding the minimum and stop when it runs out?

Show answer

Every range starting at the current minimum must reach the current maximum, so the only way to narrow it is to raise the minimum. Once that list has no more values, any range further right would miss it entirely.

Key takeaways

  • One heap entry per sequence: value plus origin. O(N log k) time, O(k) memory.
  • Skip empty sequences; compare with Integer.compare.
  • The same heap finds the smallest range covering k lists: advance the minimum's list, stop when it runs out.
  • This is how external sorts and LSM-tree compactions merge sorted runs.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Merge k sorted sequencesheap, one entry each
Python, lazilyheapq.merge
Fewest comparisonsloser tree
No heappairs in rounds
Smallest covering rangeheap + current max
Bigger than memoryruns, then k-way passes

Snippets worth knowing by heart

Java

PriorityQueue<int[]> heap = new PriorityQueue<>((x, y) -> Integer.compare(x[0], y[0]));  // {value, list, index}
for (int i = 0; i < k; i++) if (lists[i].length > 0) heap.offer(new int[] {lists[i][0], i, 0});

Python

import heapq
merged = list(heapq.merge(*lists))          # lazy, stable, O(k) memory

The traps

  • Empty sequences when seeding: reading their first value throws. Skip them.
  • Every value in the heap: memory grows to O(N) and the O(k) advantage is gone. Keep one entry per sequence.
  • No origin on entries: you can't fetch the next value. Store (value, sequence, index).
  • Subtraction comparators: x[0] - y[0] overflows for values far apart. Use Integer.compare.
  • One list at a time: merging into a growing result is O(N k). Use a heap, or merge in pairs.

Staff-level points, one line each

  • 64 runs x 1,000: Java heap 11.8 comparisons a value, heapq 6.9, loser tree 6.0, pairs in rounds 6.0, one list at a time 32.5.
  • Stable merges tie-break on the run index; heapq.merge emitted a1, a2, a3, b1, b2.
  • heapq.merge streams: two endless generators merged on demand.
  • 1 TB in 1 GB of memory: 1,000 runs, 2 passes 100-way, 10 passes 2-way.

Say it in an interview

  • "One heap entry per list, so O(N log k) time and O(k) memory."
  • "Each entry carries its list and index, so I know what to push next."
  • "Merging one by one is O(N k); the heap avoids that."
  • "External sorts make sorted runs and k-way merge them; a large k means few passes."

Practice

This skill comes with 8 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice K-Way Merge Heap free

Merge K Sorted Arrays

MediumProCoding problemJava · Python

Given k arrays, each individually sorted ascending, merge them into a single sorted array.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Smallest Range Covering K Lists

HardProCoding problemJava · Python

Each of k sorted lists holds some integers. Find the narrowest range [a, b] that contains at least one number from every list. Narrower means a smaller b - a; if two ranges are equally narrow, the one that starts lower wins.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice K-Way Merge Heap free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime