Five Lists, One Output

Imagine you're a librarian with five carts of pre-sorted books, and one empty shelf. Each cart is already in alphabetical order — A through Z — but the carts aren't sorted relative to each other. Cart 1 starts with “Algorithms.” Cart 3 starts with “Abstraction.” Which book goes on the shelf first?

Your instinct is correct: scan all five front cards, find the smallest, shelve it, then advance that cart. Simple enough for five carts. But here's what should bother you — every single time you shelve one book, you scan all five fronts again. With 5 carts that's barely noticeable. But picture 100 carts, each containing a million database records from a different shard. Scanning 100 fronts for every single record means your merge loop does 100 * 100,000,000 = 10 billion comparisons. That's not a theoretical concern — it's the difference between a query finishing in seconds versus hours.

This is the K-way merge problem. You have K sorted sources and need to produce a single sorted output. The “naive” approach — scanning all K heads at every step — costs O(NK) total comparisons, where N is the total number of elements across all lists. The question is whether we can do better.

Scrub through the merge below. Watch the comparison counter — every single element extracted costs 5 comparisons, even though most of those heads haven't changed since the last step.

L1
1
4
7
<
L2
2
5
8
<
L3
3
6
9
<
L4
10
13
<
L5
11
12
<
empty
Comparisons:0

Start: 5 sorted lists, each with a head pointer at position 0. Output is empty.

1 / 14

That repeated scanning is the waste. The question is: can a smarter data structure remember which heads are small?

Below, you have 5 sorted lists. Your job: merge them into a single sorted output. The catch? You can only look at the first unconsumed element of each list — the “head.” At every step, you must pick the smallest head. Pay attention to how many comparisons you're making. Feel the cost.

The merge machine

In the first phase, you'll merge elements by hand — scanning all five heads each time, just like that librarian. Notice how the comparison counter climbs. Notice the repetitive eye-scanning across every list, even when most of them couldn't possibly hold the minimum. By the fifth or sixth extraction, you'll start to feel the waste: your eyes dart across heads that haven't changed since your last pick, comparing values you already know are too large. That wasted work is O(K) per step, and it compounds relentlessly.

Here's the question that should gnaw at you: when you extract the minimum from list 3, only list 3's head changes. The other four heads are identical to what they were a moment ago. Why are you re-scanning all of them? What if there were a structure that remembered the relative order of those heads, so you only had to account for the one that changed?

Then the rescue arrives: a min-heap. Instead of scanning K heads manually, you'll drop them all into a heap that maintains the smallest element at the top. Extracting the minimum costs log K comparisons (to restore heap order), not K. When you push the successor from the same list, that's another log K. Two logarithmic operations instead of one linear scan — per element. The heap remembers what you keep forgetting — which heads are small and which are large — so you never re-examine a head that hasn't moved.

Watch how the comparison counter behaves differently in each phase. The gap between O(NK) and O(N log K) isn't abstract — you'll feel it in your fingers.

Tap the smallest head across all 5 lists.
L1
L2
L3
L4
L5
Merged

The Pattern

What you just experienced was the core loop that powers every K-way merge problem in existence. Strip away the specific lists and the specific numbers, and you're left with a four-step machine:

  1. Seed the heap with one element per list — exactly K entries. Not all N elements. Just K. This is why the heap stays small even when the total data is enormous.
  2. Extract the minimum — the heap's root, guaranteed to be the global minimum across all K fronts. This costs O(log K) comparisons to sift down and restore heap order.
  3. Push the successor from the same list that just contributed the minimum. This costs O(log K) to sift up. If that list is exhausted, skip this step — the heap shrinks by one.
  4. Repeat until the heap is empty, meaning all lists have been fully consumed.

Watch the heap handle one extract-min + insert cycle below. Count the comparisons — it's log K, not K.

1132237455
Comparisons:0

Min-heap with 5 elements. Root 1 is the global minimum across all K lists.

1 / 9

Total work: O(N log K), where N is the total number of elements across all K lists. The heap turned a linear scan into a logarithmic lookup — and the gap widens dramatically as K grows. With K = 5, log K is barely smaller than K. With K = 1000, you're doing 10 comparisons per element instead of 1000. That's a 100x speedup — from the same data structure you already know from heapsort.

Drag the slider to feel the gap. At K = 5 it barely matters. At K = 1000 you're doing 10 comparisons instead of 1000 — a 100x speedup from the same data structure.

K = 5N = 1,000,000
O(NK)5.0M
O(N log K)2.3M

K=5: O(NK) = 5.0M, O(N log K) = 2.3M 2.2x faster

This pattern appears every time you see “merge K sorted lists,” “smallest range covering K lists,” “K-way external sort,” or “find the kth element across K sorted arrays.” The heap is the tool that makes all of them tractable. Learn to recognize the shape: multiple sorted sources, one merged output, and a heap holding exactly K frontier elements.