Skip to content
DSA heaps 9 min read

Heap Operations: Insert, Extract & Build-Heap

Every heap rests on two repair routines: sift-up (also called bubble-up) and sift-down (bubble-down). After any change, exactly one of them restores the heap property. Insert appends then sifts up; extract removes the root then sifts down; build-heap sifts down across the whole array. Master these three and you’ve mastered heaps. The examples below build a min-heap on top of the array layout from the binary heap.

Insert: append, then sift up

To add a value, place it at the end of the array (keeping the tree complete), then repeatedly swap it with its parent while it is smaller than that parent. It rises until it finds a parent that is no larger than it — or reaches the root. Since the tree height is log n, this is O(log n).

void siftUp(std::vector<int>& a, int i) {
    while (i > 0) {
        int p = (i - 1) / 2;
        if (a[i] >= a[p]) break;     // parent is smaller-or-equal: done
        std::swap(a[i], a[p]);
        i = p;
    }
}
void insert(std::vector<int>& a, int x) {
    a.push_back(x);                  // append at the end
    siftUp(a, (int)a.size() - 1);
}

Extract-min: swap root with last, then sift down

The minimum is always at the root (a[0]). To remove it: swap the root with the last element, pop the last element off (the old min), then sift down the new root — repeatedly swapping it with its smaller child until both children are larger or it becomes a leaf. Again O(log n).

int extractMin(std::vector<int>& a) {
    int top = a[0];
    a[0] = a.back();            // move last element to root
    a.pop_back();
    int i = 0, n = (int)a.size();
    while (true) {
        int l = 2*i + 1, r = 2*i + 2, smallest = i;
        if (l < n && a[l] < a[smallest]) smallest = l;
        if (r < n && a[r] < a[smallest]) smallest = r;
        if (smallest == i) break;
        std::swap(a[i], a[smallest]);
        i = smallest;
    }
    return top;
}

Pitfall: When sifting down, always compare against the smaller (for a min-heap) of the two children — swapping with the larger child breaks the property. And remember to bound-check both child indices against the current size.

Build-heap: heapify an array in O(n)

Given an arbitrary array, you could insert each element one by one in O(n log n). But there’s a faster way: sift down every non-leaf node, starting from the last non-leaf (index n/2 - 1) and working backward to the root. Surprisingly this is O(n), not O(n log n), because most nodes are near the bottom and sift down only a step or two. (Full proof and heap sort are on the heapify & heap sort page.)

void siftDown(std::vector<int>& a, int i, int n) {
    while (true) {
        int l = 2*i + 1, r = 2*i + 2, smallest = i;
        if (l < n && a[l] < a[smallest]) smallest = l;
        if (r < n && a[r] < a[smallest]) smallest = r;
        if (smallest == i) break;
        std::swap(a[i], a[smallest]);
        i = smallest;
    }
}
void buildHeap(std::vector<int>& a) {
    int n = (int)a.size();
    for (int i = n / 2 - 1; i >= 0; i--) siftDown(a, i, n);
}

Complexity recap

OperationTimeWhy
peek (root)O(1)it’s a[0]
insertO(log n)sift up at most the tree height
extractO(log n)sift down at most the tree height
build-heapO(n)most nodes sift only a little

Going deeper: A max-heap is identical — just flip every < to > so the largest child wins. In production you rarely hand-roll this: you use a priority queue, which wraps exactly these operations.

Practice these on the heap exercises.

Last updated June 25, 2026
Was this helpful?