215. Kth Largest Element in an Array

You need the kth largest value without fully sorting. A size-k min-heap scans the array once and keeps the top-k elements at O(log k) cost per element — or go one step further with Quickselect for O(n) average time.

MediumMin-HeapQuickselectOrder StatisticsTypeScript

PROBLEM What we're solving

Given an integer array nums and an integer k, return the kth largest element in the array (not the kth distinct element). Example: nums = [3,2,1,5,6,4], k = 2 → sorted descending is [6,5,4,3,2,1], so the 2nd largest is 5. Another: nums = [3,2,3,1,2,4,5,5,6], k = 4 4.

KEY IDEA Keep only the top-k survivors in a min-heap

Insight → you only care about the largest k elements. Maintain a min-heap capped at size k. The root (minimum of the heap) is always the current kth largest. If a new element beats the root, swap it in; otherwise skip. After one pass the root is the answer — no full sort needed.

RECIPE Two approaches: min-heap vs Quickselect

Approach A — min-heap O(n log k):

  • 1 · Push every element into a min-heap; sift up to maintain heap invariant.
  • 2 · Evict the min whenever the heap exceeds size k. This preserves exactly the top-k elements.
  • 3 · Return heap[0] — the smallest of the top-k is the kth largest overall.

Approach B — Quickselect O(n) average:

  • 1 · Partitionaround a pivot so all elements > pivot are left, all < pivot are right (descending order, targeting index k-1).
  • 2 · Recurse only on the side that contains index k-1 — discard the other half entirely.
  • 3 · Done when the pivot lands exactly at index k-1.
Classic confusion → the heap approach uses a min-heap (not a max-heap) to track the top-k. A max-heap would give you the global maximum, not the kth largest. The root of the min-heap is the weakest survivor — exactly the kth largest — which is evicted first if something better arrives.

COST Complexity & alternatives

Sort then index
O(n log n)
Simple but wasteful — full sort when k is small.
Min-heap size k
O(n log k)
O(k) space; great when k << n.

Quickselect achieves O(n) average with O(1) extra space (in-place partition), but O(n²) worst case on adversarial inputs. A randomized pivot (shuffle or random pick) makes worst case vanishingly rare. The heap approach is deterministic and often preferred in interviews for clarity.

Pattern transfer → the size-k min-heap pattern also solves Top K Frequent Elements, K Closest Points to Origin, Find K Pairs with Smallest Sums, and Kth Smallest Element in a Sorted Matrix. Any time you see "top-k" or "kth" in the prompt, reach for a bounded heap.

RUN IT Min-heap of size k — root is the answer

step 0 / 11
STARTFind the 2th largest in [3, 2, 1, 5, 6, 4]. We push every element into a min-heap, evicting the minimum whenever the heap exceeds size 2. The root is always our answer candidate.
1function findKthLargest(nums: number[], k: number): number {
2 // Min-heap of size k — the root is always the kth largest seen so far.
3 const heap: number[] = [];
4
5 function push(val: number): void {
6 heap.push(val);
7 let i = heap.length - 1;
8 while (i > 0) {
9 const p = Math.floor((i - 1) / 2);
10 if (heap[p] > heap[i]) { [heap[p], heap[i]] = [heap[i], heap[p]]; i = p; }
11 else break;
12 }
13 }
14
15 function pop(): number {
16 const top = heap[0];
17 const last = heap.pop()!;
18 if (heap.length > 0) {
19 heap[0] = last;
20 let i = 0;
21 while (true) {
22 const l = 2 * i + 1, r = 2 * i + 2;
23 let s = i;
24 if (l < heap.length && heap[l] < heap[s]) s = l;
25 if (r < heap.length && heap[r] < heap[s]) s = r;
26 if (s === i) break;
27 [heap[i], heap[s]] = [heap[s], heap[i]]; i = s;
28 }
29 }
30 return top;
31 }
32
33 for (const n of nums) {
34 push(n); // add every element
35 if (heap.length > k) pop(); // evict the minimum when over-full
36 }
37 return heap[0]; // root = kth largest
38}
3
0
2
1
1
2
5
3
6
4
4
5
n
heap[0]
heap (size ≤ 2)
empty
current element (n)already scannedin top-k heap (final)
slowfast

TYPESCRIPT The solution, annotated

findKthLargest.ts
function findKthLargest(nums: number[], k: number): number {
  // Min-heap of size k — the root is always the kth largest seen so far.
  const heap: number[] = [];

  function push(val: number): void {
    heap.push(val);
    let i = heap.length - 1;
    while (i > 0) {
      const p = Math.floor((i - 1) / 2);
      if (heap[p] > heap[i]) { [heap[p], heap[i]] = [heap[i], heap[p]]; i = p; }
      else break;
    }
  }

  function pop(): number {
    const top = heap[0];
    const last = heap.pop()!;
    if (heap.length > 0) {
      heap[0] = last;
      let i = 0;
      while (true) {
        const l = 2 * i + 1, r = 2 * i + 2;
        let s = i;
        if (l < heap.length && heap[l] < heap[s]) s = l;
        if (r < heap.length && heap[r] < heap[s]) s = r;
        if (s === i) break;
        [heap[i], heap[s]] = [heap[s], heap[i]]; i = s;
      }
    }
    return top;
  }

  for (const n of nums) {
    push(n);                          // add every element
    if (heap.length > k) pop();       // evict the minimum when over-full
  }
  return heap[0];                     // root = kth largest
}

Reading it block by block

Lines 4–16 — min-heap helpers. push appends then sifts up by swapping with parent while parent is larger (min-heap invariant: parent ≤ child). pop removes the root, moves the last leaf to the top, then sifts down — swap with the smaller child to restore the invariant.
Lines 35–37 — scan every element. Push each number into the heap. Immediately after, if the heap grows beyond k, evict the current minimum. This guarantees the heap always holds at most k elements and they are the k largest seen so far.
Line 38 — return the root. After scanning all elements the heap contains exactly the top-k values. Its root is the minimum of those k values, which is by definition the kth largest element overall.
Complexity → O(n log k) time — each of the n elements is pushed once (O(log k)) and at most popped once (O(log k)). O(k) auxiliary space for the heap. Quickselect cuts time to O(n) average with O(1) space but O(n²) worst case.

INTERVIEWFollow-ups they'll ask

  • "Can you do it in O(n)?" Yes — Quickselect. Partition in-place around a pivot; recurse only on the half containing index k-1. Randomize the pivot to avoid O(n²) worst case.
  • "What if k = 1 or k = n?" Both degenerate to a single linear scan for the max or min — the heap handles these correctly but a simple pass is faster in practice.
  • "What if there are duplicates?" The heap approach handles duplicates naturally — duplicates are treated as distinct elements by position, not value. The result is still correct.
  • "Return the top-k elements instead of just the kth?" Just return all elements currently in the heap after the scan — they are already the top-k values (in arbitrary order).
  • "Streaming data?" The size-k min-heap shines here — you can process an unbounded stream and always report the current kth largest in O(log k) per element. This is exactly LeetCode 703.

OPTIMAL Min-Heap

findKthLargest.ts
function findKthLargest(nums: number[], k: number): number {
  // Min-heap of size k — the root is always the kth largest seen so far.
  const heap: number[] = [];

  function push(val: number): void {
    heap.push(val);
    let i = heap.length - 1;
    while (i > 0) {
      const p = Math.floor((i - 1) / 2);
      if (heap[p] > heap[i]) { [heap[p], heap[i]] = [heap[i], heap[p]]; i = p; }
      else break;
    }
  }

  function pop(): number {
    const top = heap[0];
    const last = heap.pop()!;
    if (heap.length > 0) {
      heap[0] = last;
      let i = 0;
      while (true) {
        const l = 2 * i + 1, r = 2 * i + 2;
        let s = i;
        if (l < heap.length && heap[l] < heap[s]) s = l;
        if (r < heap.length && heap[r] < heap[s]) s = r;
        if (s === i) break;
        [heap[i], heap[s]] = [heap[s], heap[i]]; i = s;
      }
    }
    return top;
  }

  for (const n of nums) {
    push(n);                          // add every element
    if (heap.length > k) pop();       // evict the minimum when over-full
  }
  return heap[0];                     // root = kth largest
}
Complexity → O(n log k) time — each of the n elements is pushed once (O(log k)) and at most popped once (O(log k)). O(k) auxiliary space for the heap. Quickselect cuts time to O(n) average with O(1) space but O(n²) worst case.

ALT 1 Brute force — sort, then index

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

Sort the whole array descending and read off the element at position k − 1 — the kth largest. A clean correctness baseline before reaching for a heap or quickselect.

approach-2.ts
function findKthLargest(nums: number[], k: number): number {
  // Sort descending; the kth largest is then at index k - 1.
  nums.sort((a, b) => b - a);
  return nums[k - 1];
}
Note → Dead simple and always correct, but it does O(n log n) work to fully order every element when we only need the kth. A size-k min-heap brings it to O(n log k), and quickselect averages O(n).

MNEMONIC The one-liner

"Keep a bouncer list of k VIPs. Anyone taller than the shortest VIP kicks them out and takes their spot."

TRIGGERS When you see ___ → reach for ___

"kth largest / kth smallest"bounded min-heap (or max-heap) of size k
"top-k elements"min-heap of size k; root = answer
"O(n) selection"Quickselect — partition then recurse one side
"streaming kth largest"persistent size-k min-heap, O(log k) per insert

SKELETON The reusable shape

skeleton.ts
function findKthLargest(nums: number[], k: number): number {
  const heap: number[] = [];
  // push with sift-up; pop with sift-down
  for (const n of nums) {
    push(n);
    if (heap.length > k) pop();       // evict the min when heap exceeds k
  }
  return heap[0];                     // root = kth largest
}

FLASHCARDS Tap to flip

Why a min-heap (not max-heap) for kth largest?
We want the weakest survivor at the top so we can cheaply evict it when a stronger element arrives. The root of the min-heap is the kth largest by definition once the heap holds exactly k elements.
tap to flip
1 / 7
Answer to reveal explanations. No penalty for retries.Score: 0 / 9
QUESTION 01
What is the time complexity of the min-heap approach with heap size capped at k?
QUESTION 02
Why do we use a min-heap instead of a max-heap for this problem?
QUESTION 03
Trace nums = [3,2,1,5,6,4], k = 2 with the heap approach. What does the heap contain after processing all six elements?
QUESTION 04
What is Quickselect's worst-case time complexity, and how is it avoided?
QUESTION 05
For the min-heap approach, what is the space complexity?
QUESTION 06
When is the min-heap approach particularly advantageous over Quickselect?
QUESTION 07
In Quickselect targeting the kth largest (0-indexed: index k-1 in descending order), after partitioning around a pivot at position p, you should:
QUESTION 08
#215 · Kth Largest Element in an ArrayQuickselect partitions around a pivot until the (n−k)th position is found — O(n) average. A min-heap of size k gives O(n log k) with a guaranteed bound.Which algorithmic approach does this primarily use?
QUESTION 09
#215 · Kth Largest Element in an ArrayQuickselect partitions around a pivot until the (n−k)th position is found — O(n) average. A min-heap of size k gives O(n log k) with a guaranteed bound.Which implementation correctly solves it?