HeapPriority QueueTop-KMerge K SortedTwo HeapsStreaming Median

Heap / Priority Queue — The Complete Guide

Master the Heap pattern from first principles. Learn to recognize 'top-K', 'Kth largest', and 'merge K sorted' problems instantly, evolve brute-force into O(n log k) solutions, and confidently solve every heap-based interview question.

45 min read10 sections
01

Intuition from Scratch

Why does this pattern exist?

Imagine you're a hospital triage nurse. Patients arrive continuously, but you don't treat them in arrival order — you always treat the most critical patient first. You need a system that lets you quickly find the highest-priority patient, add new arrivals, and remove the one you just treated. A sorted list would work, but re-sorting after every arrival is expensive.

That's exactly what a heap (priority queue) does. It's a data structure that gives you the minimum or maximum element in O(1) and lets you insert or remove elements in O(log n). It doesn't fully sort the data — it only guarantees that the "best" element is always at the top. This partial ordering is what makes it faster than sorting for problems where you only care about the extreme values.

In coding interviews, heaps appear whenever you need to repeatedly extract the smallest or largest element from a changing collection. The three big families are: (1) Top-K problems, (2) Merge K sorted streams, and (3) Two-heap median/balance problems.

How a Heap Works (The Basics)

A heap is a complete binary tree stored as an array. In a min-heap, every parent is ≤ its children. In a max-heap, every parent is ≥ its children. The root is always the min (or max).

Heap as Array — Index Relationshipstext
For element at index i:
  Parent:      Math.floor((i - 1) / 2)
  Left child:  2 * i + 1
  Right child: 2 * i + 2

Example min-heap:
        1
       / \
      3    5
     / \  /
    7   8 9

Array: [1, 3, 5, 7, 8, 9]

Key operations:
  peek()    → O(1)      — return root (min or max)
  insert()  → O(log n)  — add at end, bubble UP
  extract() → O(log n)  — remove root, move last to root, bubble DOWN
  heapify() → O(n)      — build heap from unsorted array (not O(n log n)!)

Analogies to Build Intuition

🏥

Hospital Emergency Room

Patients arrive at random times with different severity levels. The ER always treats the most critical patient next — not the one who arrived first. A heap is the ER's triage system: O(1) to find the most critical, O(log n) to admit or discharge a patient.

🏆

Leaderboard with K Slots

Imagine a 'Top 10' leaderboard. You don't need to sort all 1 million players — you just need to know if a new score beats the lowest score on the board. A min-heap of size K does exactly this: the root is the weakest score on the board. If a new score beats it, swap them.

🔀

Merging Sorted Playlists

You have K friends, each with a sorted playlist. To merge them into one sorted playlist, you compare the current song from each friend and pick the smallest. A min-heap of K elements lets you always pick the smallest in O(log K) instead of scanning all K lists.

What kind of problems does it solve?

Heaps are your go-to when:

  • You need the Kth largest/smallest element or the top K elements
  • You need to merge K sorted lists, arrays, or streams
  • You need a running median or balance between two halves of data
  • You need to schedule tasks by priority or deadline
  • You need to repeatedly extract the min/max from a dynamic collection
  • Sorting the entire dataset is overkill — you only need partial order

The core insight

A heap gives you the extreme element (min or max) in O(1) and maintains this guarantee through insertions and deletions in O(log n). Whenever you need "the best so far" from a changing collection, a heap is the natural choice. It's cheaper than sorting because it only maintains partial order.

02

Pattern Recognition

Recognizing when a heap is the right tool is the most important skill. Here are the signals to look for and the traps to avoid.

Keywords & Signals in the Problem Statement

Use Heap when you see...

  • "Kth largest" or "Kth smallest" element
  • "Top K" or "K most frequent" elements
  • "Merge K sorted" lists / arrays / streams
  • "Find median" from a data stream
  • "Closest K points" to origin or a target
  • "Minimum cost" to connect / schedule / process
  • "Reorganize" or "rearrange" string with frequency constraints
  • "Smallest range covering elements from K lists"
  • "Meeting rooms" — minimum rooms needed
  • Repeatedly needing the min or max from a dynamic set

Don't use Heap when...

  • You need the min/max of a fixed sliding window — use Monotonic Deque
  • You need all elements sorted — just sort the array in O(n log n)
  • You need the next greater/smaller element — use Monotonic Stack
  • You need O(1) lookup by key — use a HashMap
  • The problem is about contiguous subarrays — use Sliding Window
  • You only need the overall min/max once — a single pass suffices

Heap vs. Similar Patterns

PatternWhen to UseKey Difference
Heap / Priority QueueTop-K, Kth element, merge K sorted, streaming min/maxO(log n) insert/extract; maintains partial order — only the extreme is guaranteed
SortingNeed all elements in order, or need to process in sorted order onceO(n log n) upfront, then O(1) access. Better when you sort once and query many times
QuickSelectFind the Kth element in an unsorted array (one-time query)O(n) average for a single Kth element query — faster than heap for one-shot
Monotonic DequeSliding window min/max over a fixed windowO(1) amortized per slide. Heap would be O(log n) per slide — deque wins
BST / TreeMapNeed min, max, AND arbitrary lookups/deletionsO(log n) for all operations including search. Heap can't search efficiently

The heap question to always ask

When you see a problem, ask: "Do I repeatedly need the smallest or largest element from a collection that changes over time?" If yes, reach for a heap. The key word is "repeatedly" — if you only need the extreme once, a single pass or QuickSelect is cheaper.

03

Brute Force → Optimized Thinking

Let's walk through the thinking evolution using a concrete example: Kth Largest Element in an Array — given an unsorted array and integer k, find the kth largest element.

1

Brute Force — Sort the Entire Array

O(n log n) time · O(1) space (in-place sort)

Sort the array in descending order and return the element at index k-1. Simple, but you're sorting the entire array when you only need one element.

Brute Force — Full Sorttypescript
function findKthLargest(nums: number[], k: number): number {
  nums.sort((a, b) => b - a);
  return nums[k - 1];
}
// Problem: We sort ALL n elements just to find ONE element.
// O(n log n) is overkill when k is small relative to n.
2

Better — Min-Heap of Size K

O(n log k) time · O(k) space

Maintain a min-heap of size k. The root is always the smallest among the k largest elements seen so far — which is exactly the kth largest. For each new element, if it's bigger than the root, swap them.

Min-Heap of Size K — Optimal for Streamingtypescript
function findKthLargest(nums: number[], k: number): number {
  // MinPriorityQueue from @datastructures-js/priority-queue
  // or implement your own min-heap
  const minHeap = new MinPriorityQueue<number>();

  for (const num of nums) {
    minHeap.enqueue(num);
    // If heap exceeds size k, remove the smallest
    if (minHeap.size() > k) {
      minHeap.dequeue();
    }
  }

  // Root of min-heap = kth largest
  return minHeap.front();
}
// Why min-heap? The root is the SMALLEST of the k largest.
// That smallest-of-the-largest IS the kth largest element.
// Time: O(n log k) — each of n elements does O(log k) heap ops.
// Space: O(k) — heap never exceeds size k.
3

Best (One-Shot) — QuickSelect

O(n) average · O(n) worst case · O(1) space

For a single query on a static array, QuickSelect (partition-based) finds the kth element in O(n) average time. But for streaming data or repeated queries, the heap approach is better.

QuickSelect — O(n) Averagetypescript
function findKthLargest(nums: number[], k: number): number {
  const target = nums.length - k; // kth largest = (n-k)th smallest

  function quickSelect(left: number, right: number): number {
    const pivot = nums[right];
    let storeIdx = left;
    for (let i = left; i < right; i++) {
      if (nums[i] <= pivot) {
        [nums[i], nums[storeIdx]] = [nums[storeIdx], nums[i]];
        storeIdx++;
      }
    }
    [nums[storeIdx], nums[right]] = [nums[right], nums[storeIdx]];

    if (storeIdx === target) return nums[storeIdx];
    if (storeIdx < target) return quickSelect(storeIdx + 1, right);
    return quickSelect(left, storeIdx - 1);
  }

  return quickSelect(0, nums.length - 1);
}
// O(n) average, but O(n²) worst case without randomization.
// Great for one-shot queries. Heap is better for streaming.

Why Does the Heap Optimization Work?

1

You only need partial order, not full sort

Sorting gives you ALL elements in order — O(n log n). But you only need the kth largest. A heap of size k maintains just enough order to answer that question, reducing the work to O(n log k).

2

The min-heap acts as a 'bouncer' for the top-K club

Think of the heap as a VIP section with k seats. The bouncer (root) is the weakest member. When someone stronger arrives, the bouncer gets kicked out and the newcomer enters. At the end, the bouncer IS the kth strongest — the answer.

3

Log k vs log n makes a real difference

When k is much smaller than n (e.g., top 10 out of 1 million), O(n log k) ≈ O(n log 10) ≈ O(3.3n), while O(n log n) ≈ O(20n). The heap approach is ~6x faster in practice.

The thinking process matters more than the code

In interviews, walk through this progression: "Sorting gives O(n log n), but I only need the kth element. A min-heap of size k lets me process each element in O(log k), giving O(n log k). For a one-shot query, QuickSelect is O(n) average, but the heap approach generalizes to streaming data." This shows the interviewer you understand the tradeoffs.

04

Core Templates

Heap problems follow a few recurring templates. Memorize these structures — most problems are variations of one of them.

Template 1: Top-K Elements (Min-Heap of Size K)

To find the K largest elements, maintain a min-heap of size K. The root is the smallest of the K largest — anything smaller gets rejected. Works for Kth largest, K most frequent, K closest, etc.

Top-K Templatetypescript
function topK(items: Item[], k: number): Item[] {
  const minHeap = new MinPriorityQueue<Item>({
    compare: (a, b) => score(a) - score(b),
  });

  for (const item of items) {
    minHeap.enqueue(item);
    if (minHeap.size() > k) {
      minHeap.dequeue(); // remove the weakest
    }
  }

  // Heap now contains the K best items
  return minHeap.toArray();
}
// Time: O(n log k)  Space: O(k)
// Key: min-heap for top-K largest, max-heap for top-K smallest
// The root is always the "weakest" member of the top-K club

Template 2: Merge K Sorted Lists/Streams

Put the first element from each of K sorted sources into a min-heap. Extract the minimum, then push the next element from that same source. Repeat until all sources are exhausted.

Merge K Sorted Templatetypescript
function mergeKSorted(lists: number[][]): number[] {
  const minHeap = new MinPriorityQueue<{val: number, listIdx: number, elemIdx: number}>({
    compare: (a, b) => a.val - b.val,
  });

  // Seed heap with first element from each list
  for (let i = 0; i < lists.length; i++) {
    if (lists[i].length > 0) {
      minHeap.enqueue({ val: lists[i][0], listIdx: i, elemIdx: 0 });
    }
  }

  const result: number[] = [];
  while (minHeap.size() > 0) {
    const { val, listIdx, elemIdx } = minHeap.dequeue();
    result.push(val);

    // Push next element from the same list
    if (elemIdx + 1 < lists[listIdx].length) {
      minHeap.enqueue({
        val: lists[listIdx][elemIdx + 1],
        listIdx,
        elemIdx: elemIdx + 1,
      });
    }
  }

  return result;
}
// Time: O(N log K) where N = total elements, K = number of lists
// Space: O(K) — heap holds at most K elements at any time
// Key: heap always has at most one element per source list

Template 3: Two Heaps (Median / Balance)

Split data into two halves: a max-heap for the lower half and a min-heap for the upper half. The median is at the top of one or both heaps. Rebalance after each insertion to keep sizes within 1.

Two Heaps — Streaming Median Templatetypescript
class MedianFinder {
  private lo = new MaxPriorityQueue<number>(); // max-heap: lower half
  private hi = new MinPriorityQueue<number>(); // min-heap: upper half

  addNum(num: number): void {
    // Step 1: Add to appropriate heap
    if (this.lo.size() === 0 || num <= this.lo.front()) {
      this.lo.enqueue(num);
    } else {
      this.hi.enqueue(num);
    }

    // Step 2: Rebalance — sizes differ by at most 1
    if (this.lo.size() > this.hi.size() + 1) {
      this.hi.enqueue(this.lo.dequeue());
    } else if (this.hi.size() > this.lo.size()) {
      this.lo.enqueue(this.hi.dequeue());
    }
  }

  findMedian(): number {
    if (this.lo.size() > this.hi.size()) {
      return this.lo.front();
    }
    return (this.lo.front() + this.hi.front()) / 2;
  }
}
// Time: O(log n) per addNum, O(1) per findMedian
// Space: O(n)
// Key: lo.size() === hi.size() or lo.size() === hi.size() + 1
// Median is always at lo.front() or average of both fronts

Template 4: Greedy with Heap (Schedule / Process)

When you need to greedily pick the best option at each step (cheapest cost, earliest deadline, highest frequency), a heap lets you always grab the optimal choice in O(log n).

Greedy Heap — Task Scheduling Templatetypescript
function minCostProcess(tasks: Task[]): number {
  const minHeap = new MinPriorityQueue<Task>({
    compare: (a, b) => a.cost - b.cost,
  });

  // Load all tasks
  for (const task of tasks) {
    minHeap.enqueue(task);
  }

  let totalCost = 0;
  while (minHeap.size() > 1) {
    // Greedily pick the cheapest options
    const first = minHeap.dequeue();
    const second = minHeap.dequeue();

    const combined = combine(first, second);
    totalCost += combined.cost;

    // Push the result back for future processing
    minHeap.enqueue(combined);
  }

  return totalCost;
}
// When to use: connect ropes/sticks, Huffman coding,
// minimum cost to merge stones, task scheduler
// Key: greedy choice + re-insertion into heap

Which template to pick?

Ask yourself: (1) Do I need the K best elements? → Template 1. (2) Am I merging K sorted sources? → Template 2. (3) Do I need a running median or balance between halves? → Template 3. (4) Do I greedily pick the best option and re-insert? → Template 4.

05

Variations & Sub-Patterns

The heap isn't a single trick — it's a family of techniques. Here are the most common variations and how the approach changes for each.

VariationHeap StrategyClassic Example
Top-K LargestMin-heap of size K — root is the Kth largestKth Largest Element, Top K Frequent Elements
Top-K SmallestMax-heap of size K — root is the Kth smallestK Closest Points to Origin
Merge K SortedMin-heap of K elements, one per source — always extract global minMerge K Sorted Lists, Smallest Range Covering K Lists
Two Heaps (Median)Max-heap for lower half + min-heap for upper half — median at topsFind Median from Data Stream, Sliding Window Median
Greedy SchedulingHeap orders tasks by priority/deadline — greedily pick the bestTask Scheduler, Meeting Rooms II, Reorganize String
Lazy DeletionMark elements as deleted but don't remove until they reach the topSliding Window Median, Design Twitter
Heap + BFS (Dijkstra-like)Min-heap as frontier — always expand the cheapest nodeNetwork Delay Time, Swim in Rising Water, Path With Minimum Effort
Custom ComparatorHeap with multi-key comparison for complex orderingReorganize String, Sort Characters By Frequency

Problems mentioned above

Deep Dive: Top K Frequent Elements

Given an array, return the k most frequent elements. This combines hashing (frequency counting) with a heap (top-K selection).

Top K Frequent Elementstypescript
function topKFrequent(nums: number[], k: number): number[] {
  // Step 1: Count frequencies — O(n)
  const freq = new Map<number, number>();
  for (const num of nums) {
    freq.set(num, (freq.get(num) ?? 0) + 1);
  }

  // Step 2: Min-heap of size k, ordered by frequency
  const minHeap = new MinPriorityQueue<[number, number]>({
    compare: (a, b) => a[1] - b[1], // compare by frequency
  });

  for (const [num, count] of freq) {
    minHeap.enqueue([num, count]);
    if (minHeap.size() > k) {
      minHeap.dequeue(); // remove least frequent
    }
  }

  // Step 3: Extract results
  return minHeap.toArray().map(([num]) => num);
}
// Time: O(n log k) — n for counting, m log k for heap (m = unique elements)
// Space: O(n) for frequency map + O(k) for heap
// Alternative: Bucket sort gives O(n) but uses O(n) space

Deep Dive: Task Scheduler

Given tasks with a cooldown period n between same tasks, find the minimum intervals needed. The greedy strategy: always schedule the most frequent task first to minimize idle time.

Task Schedulertypescript
function leastInterval(tasks: string[], n: number): number {
  // Count frequencies
  const freq = new Map<string, number>();
  for (const task of tasks) {
    freq.set(task, (freq.get(task) ?? 0) + 1);
  }

  // Max-heap by frequency
  const maxHeap = new MaxPriorityQueue<number>();
  for (const count of freq.values()) {
    maxHeap.enqueue(count);
  }

  let time = 0;
  while (maxHeap.size() > 0) {
    const cooldown: number[] = [];
    // Process up to n+1 tasks in this cycle
    for (let i = 0; i <= n; i++) {
      if (maxHeap.size() > 0) {
        const count = maxHeap.dequeue();
        if (count - 1 > 0) {
          cooldown.push(count - 1);
        }
      }
      time++;
      // If both heap and cooldown are empty, we're done
      if (maxHeap.size() === 0 && cooldown.length === 0) break;
    }
    // Re-add tasks that still have remaining count
    for (const count of cooldown) {
      maxHeap.enqueue(count);
    }
  }

  return time;
}
// Time: O(n × totalTasks) in worst case, but typically much faster
// Key insight: greedily pick the most frequent task each cycle
// to minimize idle slots
06

Visual Walkthrough

Let's trace through two key heap problems step by step to build deep intuition.

Kth Largest Element — Min-Heap of Size K

Input: nums = [3, 2, 1, 5, 6, 4], k = 2. Find the 2nd largest element.

Kth Largest — Step-by-Steptext
nums = [3, 2, 1, 5, 6, 4], k = 2
Min-heap of size 2root = kth largest

Process 3: heap = [3]           size=1 (< k, just add)
Process 2: heap = [2, 3]        size=2 (= k, heap is full)
Process 1: 1 < root(2)?         YESskip (too small for top-2)
           heap = [2, 3]
Process 5: 5 > root(2)?         YESadd, remove root
           heap = [3, 5]        root=3 (new kth largest)
Process 6: 6 > root(3)?         YESadd, remove root
           heap = [5, 6]        root=5 (new kth largest)
Process 4: 4 < root(5)?         YESskip (too small for top-2)
           heap = [5, 6]

Answer: root = 5 (2nd largest)

Key observations:
- Heap never exceeds size k=2
- Root is always the SMALLEST of the top-kthe kth largest
- Elements smaller than root are immediately rejected
- Each insertion/removal is O(log k) = O(log 2) = O(1) here

Find Median from Data Stream — Two Heaps

Stream: [5, 15, 1, 3]. Find the running median after each insertion.

Two Heaps Median — Step-by-Steptext
lo = max-heap (lower half)    hi = min-heap (upper half)
Rule: lo.size >= hi.size, and lo.size <= hi.size + 1

Add 5:
  lo = [5]    hi = []
  Balance: lo.size(1) = hi.size(0) + 1
  Median = lo.top = 5

Add 15:
  15 > lo.top(5) → add to hi
  lo = [5]    hi = [15]
  Balance: lo.size(1) = hi.size(1) ✓
  Median = (5 + 15) / 2 = 10

Add 1:
  1 <= lo.top(5) → add to lo
  lo = [5, 1]    hi = [15]
  Balance: lo.size(2) = hi.size(1) + 1
  Median = lo.top = 5

Add 3:
  3 <= lo.top(5) → add to lo
  lo = [5, 3, 1]    hi = [15]
  Balance: lo.size(3) > hi.size(1) + 1REBALANCE
  Move lo.top(5) to hi
  lo = [3, 1]    hi = [5, 15]
  Balance: lo.size(2) = hi.size(2) ✓
  Median = (3 + 5) / 2 = 4

Visual at the end:
  Lower half (max-heap):  [3, 1]     ← top is 3
  Upper half (min-heap):  [5, 15]    ← top is 5
  
  All sorted: 1, 3 | 5, 15
                  ↑   ↑
              lo.top  hi.topmedian = (3+5)/2 = 4

Key observations:
- lo always holds the smaller half, hi the larger half
- lo.tophi.top (the halves don't overlap)
- Median is always accessible in O(1) from the tops
- Rebalancing is O(log n) but happens at most once per insertion
07

Practice Problems

These 7 problems are carefully ordered from easy to hard. Each one teaches a different aspect of the Heap pattern. Solve them in order.

1

LC 703. Kth Largest Element in a Stream

Easy

Why this pattern:

Pure top-K template. Maintain a min-heap of size k. On each add(), push the new value and pop if size exceeds k. The root is always the kth largest.

Key idea: Initialize the heap with the first elements. For each new value: enqueue, then dequeue if size > k. Return heap.front(). This is the simplest possible heap problem — start here.

2

LC 1046. Last Stone Weight

Easy

Why this pattern:

Greedy with max-heap. Repeatedly extract the two heaviest stones, smash them, and push the remainder back. The heap always gives you the heaviest stone in O(log n).

Key idea: Build a max-heap from all stones. While size > 1: pop two, push their difference (if non-zero). The last remaining element (or 0) is the answer.

3

LC 973. K Closest Points to Origin

Medium

Why this pattern:

Top-K smallest distances. Use a max-heap of size k — the root is the farthest of the k closest. Any point closer than the root replaces it.

Key idea: For each point, compute distance² (avoid sqrt). Maintain a max-heap of size k ordered by distance. If a new point is closer than the root, swap. Final heap = k closest points.

4

LC 347. Top K Frequent Elements

Medium

Why this pattern:

Hashing + Top-K. First count frequencies with a hash map, then use a min-heap of size k to find the k most frequent elements.

Key idea: Build frequency map. For each (element, count) pair: push to min-heap (ordered by count), pop if size > k. The heap retains the k highest-frequency elements.

5

LC 295. Find Median from Data Stream

Hard

Why this pattern:

Two-heap pattern. Max-heap for the lower half, min-heap for the upper half. Median is at the top of one or both heaps. Rebalance after each insertion.

Key idea: Add to lo (max-heap) if num ≤ lo.top, else to hi (min-heap). Rebalance so lo.size is equal to or one more than hi.size. Median = lo.top (odd count) or average of both tops (even count).

6

LC 23. Merge K Sorted Lists

Hard

Why this pattern:

Merge-K template. Min-heap of size K holds one node from each list. Extract the global minimum, advance that list's pointer, push the next node.

Key idea: Seed heap with head of each list. While heap is non-empty: pop min node, append to result, push node.next if it exists. Heap size never exceeds K, so each operation is O(log K).

7

LC 632. Smallest Range Covering Elements from K Lists

Hard

Why this pattern:

Merge-K variant with a twist. Min-heap tracks the current smallest across K lists. Simultaneously track the current maximum. The range [heap.min, currentMax] covers one element from each list.

Key idea: Seed heap with first element of each list, track the max. The range is [heap.top, max]. Pop the min, push the next from that list, update max. Track the smallest range seen. Stop when any list is exhausted.

Practice strategy

For each problem: (1) Identify which heap template applies before coding. (2) Decide min-heap vs max-heap and why. (3) Determine the heap size constraint (fixed K or unbounded). (4) After solving, write one sentence: "This is [template name] because [signal]."

08

Common Mistakes

These are the traps that catch people on Heap problems. Learn them here so you don't learn them in an interview.

🔄

Using max-heap when you need min-heap (and vice versa)

For 'top K largest', you need a MIN-heap of size K (root = kth largest = the weakest in the top-K club). Using a max-heap of size K gives you the largest element, not the kth largest.

Remember: top-K LARGEST → min-heap (root is the bouncer — the weakest member). Top-K SMALLEST → max-heap (root is the weakest = the largest of the small ones). The heap type is always the OPPOSITE of what you're looking for.

📏

Forgetting to cap the heap size at K

You push all n elements into the heap without removing any. The heap grows to size n, and you lose the O(n log k) advantage — it becomes O(n log n), same as sorting.

After every enqueue, check: if (heap.size() > k) heap.dequeue(). This keeps the heap at size k and ensures O(log k) per operation.

⚖️

Not rebalancing in the two-heap pattern

After inserting into one of the two heaps, you forget to rebalance. The heaps become lopsided, and the median calculation breaks.

After every insertion, check: if lo.size > hi.size + 1, move lo.top to hi. If hi.size > lo.size, move hi.top to lo. Always enforce the size invariant.

🔢

Wrong comparator direction

In languages where you provide a custom comparator (JavaScript, Python), getting the sign wrong flips min-heap to max-heap or vice versa. Your results are silently wrong.

For min-heap: compare(a, b) => a - b (smaller values have higher priority). For max-heap: compare(a, b) => b - a. Test with 3 elements to verify the order before proceeding.

🗑️

Trying to remove arbitrary elements from a heap

Standard heaps only support removing the root efficiently. Trying to remove an element from the middle is O(n) — you have to find it first.

Use lazy deletion: mark the element as deleted, but only actually remove it when it reaches the top. Or use a balanced BST (TreeMap) if you need arbitrary deletions in O(log n).

⏱️

Using heap when a simpler approach works

You reach for a heap to find the single maximum element, or to sort an array. A single pass for max, or Arrays.sort(), is simpler and equally fast.

Heap shines when you REPEATEDLY need the extreme from a CHANGING collection. For one-time queries, consider: single pass (min/max), sorting (full order), or QuickSelect (kth element).

09

Interview Strategy

Knowing the pattern is half the battle. Here's how to present it in an interview setting to maximize your score.

The 5-Step Interview Flow

1

Clarify & Identify the Signal

'I need the kth largest / top K / merge K sorted / running median?' — Identifying the heap sub-pattern early narrows your approach. Ask: 'Is the data streaming or static? Do I need repeated queries or just one?'

2

State the Brute Force and Its Cost

'Sorting gives O(n log n). But I only need the top K, so I can use a heap of size K for O(n log k).' Or: 'Merging K lists by repeated linear scan is O(N×K), but a heap reduces each extraction to O(log K), giving O(N log K).'

3

Choose Min-Heap vs Max-Heap

Be explicit: 'I'll use a min-heap of size K because the root will be the Kth largest — the weakest member of the top-K club. Anything smaller than the root gets rejected.' This shows you understand WHY, not just WHAT.

4

Code It Clean

Use a priority queue abstraction (don't implement the heap from scratch unless asked). Name variables clearly: minHeap, maxHeap, lo, hi. Comment the invariant: '// heap.size() <= k at all times'.

5

Test & Analyze

Trace through a small example showing elements entering and leaving the heap. State complexity: 'O(n log k) time because each of n elements does at most one O(log k) insertion and one O(log k) extraction. O(k) space for the heap.'

Common Follow-Up Questions

Follow-UpHow to Handle
"Can you do it in O(n) instead of O(n log k)?"For a static array: yes, QuickSelect gives O(n) average. For streaming data: no, O(n log k) is optimal. Mention bucket sort if frequencies are bounded.
"What if K is very close to N?"Flip the approach: instead of a min-heap of size K for top-K largest, use a max-heap of size (N-K) for the (N-K) smallest. Whichever K is smaller wins.
"What if the data is streaming (infinite)?"Heap is perfect for streaming — you process each element in O(log k) and never need to store more than k elements. Sorting doesn't work because you can't sort an infinite stream.
"Can you implement the heap from scratch?"Yes — show the array-based implementation with bubbleUp (insert) and bubbleDown (extract). Key: parent = (i-1)/2, children = 2i+1 and 2i+2. This is a common follow-up at FAANG.
"What about the sliding window median?"Two heaps + lazy deletion. When the window slides, mark the outgoing element as deleted. Only actually remove it when it reaches the top of a heap. Rebalance after each step.
"How would you handle duplicates?"Heaps handle duplicates naturally — they're just treated as separate elements. For frequency-based problems, use a (value, count) pair in the heap.

The meta-skill

Interviewers aren't just testing if you know heaps. They're testing if you can (1) recognize the "repeated extreme extraction" signal, (2) choose the right heap type and size, and (3) explain the tradeoff vs sorting or QuickSelect. Practice saying "I use a min-heap of size K because..." out loud until it's automatic.

10

Summary & Cheat Sheet

Pin this section. Review it before mock interviews and contests.

Quick Revision Cheat Sheet

Core idea: A heap gives O(1) access to the min/max and O(log n) insert/extract. Use it when you repeatedly need the extreme from a changing collection.

Top-K largest: Min-heap of size K. Root = Kth largest (the bouncer). Reject anything smaller than root. O(n log k).

Top-K smallest: Max-heap of size K. Root = Kth smallest. Reject anything larger than root. O(n log k).

Merge K sorted: Min-heap of K elements (one per source). Extract global min, push next from same source. O(N log K).

Two heaps (median): Max-heap (lower half) + min-heap (upper half). Median at tops. Rebalance after each insert. O(log n) per add.

Greedy + heap: Always pick the best option (cheapest/most frequent) via heap. Re-insert modified items. O(n log n).

Min vs max confusion: Top-K LARGEST → min-heap. Top-K SMALLEST → max-heap. The heap type is the OPPOSITE of what you seek.

Heap vs sort: Heap: O(n log k) for top-K. Sort: O(n log n). Heap wins when k << n. Sort wins when you need full order.

Heap vs QuickSelect: QuickSelect: O(n) avg for one-shot Kth element. Heap: O(n log k) but works for streaming data.

Mental trigger: "Kth largest", "top K", "merge K sorted", "running median", "minimum cost" → Heap

Decision Flowchart

When to Use a Heap — Decision Treetext
Do you REPEATEDLY need the min or max from a CHANGING collection?
├── NONot a heap problem. Consider sorting, single pass, or QuickSelect.
└── YESWhat specifically do you need?
          ├── Top-K or Kth element?
          │   ├── K largestMin-heap of size K
          │   └── K smallestMax-heap of size K
          ├── Merge K sorted sources?
          │   └── Min-heap of K elements (one per source)
          ├── Running median or balance between halves?
          │   └── Two heaps: max-heap (lower) + min-heap (upper)
          └── Greedy: always pick the best option?
              └── Heap ordered by priority/cost/frequency

Which heap type?
├── Need the SMALLEST at the topMin-heap
├── Need the LARGEST at the topMax-heap
└── Need BOTH extremesTwo heaps

Heap size?
├── Fixed at K (top-K problems)  → Cap with: if (size > k) dequeue()
├── Fixed at K (merge-K)         → One element per source, never exceeds K
├── Unbounded (greedy/scheduling) → Grows and shrinks as items are processed
└── Two heaps (median)           → Combined size = n, eachn/2

One last thing

Heap is one of the most versatile intermediate patterns. It appears in easy problems (Last Stone Weight) and hard ones (Smallest Range Covering K Lists). The difference isn't the data structure — it's recognizing the "repeated extreme extraction" signal and choosing the right template. Solve the 7 practice problems in Section 07, and you'll have that intuition locked in.