Sift-Up and Sift-Down Keep the Heap Property in O(log n)
Min-Heap and Max-Heap
Min-heaps and max-heaps differ only in their comparison direction; sift-up after push and sift-down after pop restore the heap property in O(log n) time.
What you'll learn
- Implement a MinHeap class with push, pop, and peek in JavaScript
- Trace sift-up and sift-down on a concrete example
- Convert a MinHeap into a MaxHeap by flipping one comparison
JavaScript has no built-in heap, so we build one on top of a plain array. The only difference between a min-heap and a max-heap is which value “wins” a comparison — every other line of code is identical.
Sift-Up (after push)
When a new value is appended to the end of the array it may be smaller than its parent, violating the min-heap property. Sift-up repeatedly swaps the node with its parent while the parent is larger:
class MinHeap {
#data = [];
get size() { return this.#data.length; }
peek() { return this.#data[0]; } // O(1)
push(val) {
this.#data.push(val);
this.#siftUp(this.#data.length - 1);
}
#siftUp(i) {
const d = this.#data;
while (i > 0) {
const p = (i - 1) >> 1; // parent index
if (d[p] <= d[i]) break; // heap property satisfied
[d[p], d[i]] = [d[i], d[p]]; // swap
i = p;
}
}
} Sift-Down (after pop)
Pop removes the root (the minimum). To fill the gap, move the last element to index 0 and sift-down: repeatedly swap it with the smaller of its two children until it is smaller than both children or reaches a leaf:
pop() {
const d = this.#data;
if (d.length === 0) return undefined;
const min = d[0];
const last = d.pop();
if (d.length > 0) {
d[0] = last;
this.#siftDown(0);
}
return min;
}
#siftDown(i) {
const d = this.#data;
const n = d.length;
while (true) {
let smallest = i;
const l = 2 * i + 1;
const r = 2 * i + 2;
if (l < n && d[l] < d[smallest]) smallest = l;
if (r < n && d[r] < d[smallest]) smallest = r;
if (smallest === i) break;
[d[smallest], d[i]] = [d[i], d[smallest]];
i = smallest;
}
} Complete MinHeap
class MinHeap {
#data = [];
get size() { return this.#data.length; }
peek() { return this.#data[0]; }
push(val) {
this.#data.push(val);
this.#siftUp(this.#data.length - 1);
}
pop() {
const d = this.#data;
if (!d.length) return undefined;
const min = d[0];
const last = d.pop();
if (d.length) { d[0] = last; this.#siftDown(0); }
return min;
}
#siftUp(i) {
const d = this.#data;
while (i > 0) {
const p = (i - 1) >> 1;
if (d[p] <= d[i]) break;
[d[p], d[i]] = [d[i], d[p]];
i = p;
}
}
#siftDown(i) {
const d = this.#data;
const n = d.length;
while (true) {
let s = i;
const l = 2 * i + 1, r = 2 * i + 2;
if (l < n && d[l] < d[s]) s = l;
if (r < n && d[r] < d[s]) s = r;
if (s === i) break;
[d[s], d[i]] = [d[i], d[s]];
i = s;
}
}
}
// Usage
const h = new MinHeap();
[5, 3, 8, 1, 4].forEach(v => h.push(v));
console.log(h.peek()); // 1
console.log(h.pop()); // 1
console.log(h.pop()); // 3 Converting to a MaxHeap
Flip every < to > (and <= to >=) in the sift methods. Only the
comparisons change — the index arithmetic is identical.
Up Next
Learn how to build a heap from an existing array in O(n) time — faster than pushing elements one by one.
Heapify — Building a Heap in O(n) →