Heap functions
Each snippet below is something you will reach for while solving priority-queue problems. Skim the short description, then copy the patterns for common situations.
Prefer Overview for how heaps work, and Problems for practice once problems are linked.
Index helpers
Parent and child positions are pure arithmetic on the array. Memorize these three.
parent()
Returns the parent index of i. Root has no parent — guard i > 0 before calling.
function parent(i: number): number {
return Math.floor((i - 1) / 2);
}
// ——— examples ———
parent(1); // 0
parent(2); // 0
parent(5); // 2left() / right()
Returns the left and right child indexes. Check against heap.length before reading.
function left(i: number): number {
return 2 * i + 1;
}
function right(i: number): number {
return 2 * i + 2;
}
// ——— examples ———
left(0); // 1
right(0); // 2
left(1); // 3swap()
Exchange two slots. Used by every sift step.
function swap(heap: number[], a: number, b: number): void {
const tmp = heap[a]!;
heap[a] = heap[b]!;
heap[b] = tmp;
}Push + sift-up
Append at the end, then bubble toward the root while the child outranks its parent.
siftUp()
Walk from i toward the root, swapping while heap[i] is better than its parent.
function siftUp(heap: number[], i: number): void {
while (i > 0) {
const p = Math.floor((i - 1) / 2);
if (heap[i]! >= heap[p]!) break; // min-heap
swap(heap, i, p);
i = p;
}
}push()
Add a value in O(log n). Append, then sift up.
function push(heap: number[], value: number): void {
heap.push(value);
siftUp(heap, heap.length - 1);
}
const heap: number[] = [];
push(heap, 5);
push(heap, 2);
push(heap, 9);
// heap[0] === 2Peek, size & empty
The root is always index 0. Reading it never moves anything.
peek()
Returns the current best value, or undefined when the heap is empty.
function peek(heap: number[]): number | undefined {
return heap[0];
}
const heap = [2, 5, 3];
peek(heap); // 2size() / isEmpty()
Length checks you will use before every pop.
function size(heap: number[]): number {
return heap.length;
}
function isEmpty(heap: number[]): boolean {
return heap.length === 0;
}Pop + sift-down
Remove the root, park the last value in its place, then slide that value down.
siftDown()
From i, repeatedly swap with the better child until the invariant holds.
function siftDown(heap: number[], i: number): void {
const n = heap.length;
while (true) {
const l = 2 * i + 1;
const r = 2 * i + 2;
let best = i;
if (l < n && heap[l]! < heap[best]!) best = l;
if (r < n && heap[r]! < heap[best]!) best = r;
if (best === i) break;
swap(heap, i, best);
i = best;
}
}pop()
Extract-min (or max). O(log n). Returns undefined on an empty heap.
function pop(heap: number[]): number | undefined {
if (heap.length === 0) return undefined;
if (heap.length === 1) return heap.pop();
const best = heap[0]!;
heap[0] = heap.pop()!;
siftDown(heap, 0);
return best;
}Heapify
Build a heap from an unordered array in O(n) by sifting parents bottom-up.
heapify()
Mutates the array in place into a valid min-heap.
function heapify(heap: number[]): void {
const n = heap.length;
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
siftDown(heap, i);
}
}
const a = [9, 5, 3, 2, 7];
heapify(a);
// a[0] is the minimumfromArray()
Copy then heapify when you must not mutate the input.
function fromArray(values: readonly number[]): number[] {
const heap = values.slice();
heapify(heap);
return heap;
}Top-K pattern
Keep a bounded heap of size k. For “k largest,” use a min-heap of the winners so far.
kLargest()
Stream values; if the heap is full and the next value beats the root, replace and sift.
function kLargest(nums: number[], k: number): number[] {
const heap: number[] = [];
for (const v of nums) {
if (heap.length < k) {
push(heap, v); // min-heap of current top-k
continue;
}
if (v > heap[0]!) {
heap[0] = v;
siftDown(heap, 0);
}
}
return heap.sort((a, b) => b - a);
}kSmallest()
Same idea with a max-heap of size k (compare flipped).
function kSmallest(nums: number[], k: number): number[] {
// Max-heap of size k: root is the largest among the small ones.
const heap: number[] = [];
for (const v of nums) {
if (heap.length < k) {
pushMax(heap, v);
continue;
}
if (v < heap[0]!) {
heap[0] = v;
siftDownMax(heap, 0);
}
}
return heap.sort((a, b) => a - b);
}Priority queue uses
Heaps power Dijkstra, merge-k-lists, and any “always take the next best” loop.
Dijkstra frontier
Store [distance, node] pairs; pop the smallest distance each step. (Sketch — wire your graph types.)
type Edge = { to: number; weight: number };
// Min-heap of [dist, node]
function dijkstraSketch(graph: Edge[][], start: number): number[] {
const dist = graph.map(() => Infinity);
dist[start] = 0;
const pq: number[][] = [[0, start]]; // heapify by dist
while (pq.length > 0) {
const [d, u] = popPair(pq)!;
if (d !== dist[u]) continue; // stale entry
for (const { to, weight } of graph[u]!) {
const nd = d + weight;
if (nd < dist[to]!) {
dist[to] = nd;
pushPair(pq, [nd, to]);
}
}
}
return dist;
}Typed MinHeap class
Wrap the helpers when a problem wants a reusable priority queue.
class MinHeap {
private data: number[] = [];
get size(): number {
return this.data.length;
}
peek(): number | undefined {
return this.data[0];
}
push(value: number): void {
push(this.data, value);
}
pop(): number | undefined {
return pop(this.data);
}
}