A heap is a tree-based data structure that retrieves the highest-priority element in constant time. "Priority" can mean the largest value, the smallest value, or any custom ordering (such as earliest deadline first).
Heaps guarantee O(log n) time for insertion and removal, and O(1) time for accessing the highest-priority element.
These properties make heaps useful in many real-world systems, including:
This chapter covers:
A heap is a binary tree optimized for fast access to the highest-priority element.
It follows two key rules:
This is what keeps the tree balanced and ensures operations like insertion and deletion are efficient.
Every heap must also satisfy the heap property, which comes in two flavors:
Even though a heap is conceptually a binary tree, we don't usually store it using nodes and pointers like a typical tree. Instead, the most common and efficient way to represent a heap is with a simple array.
Because a heap is a complete binary tree (filled level by level, left to right), it maps perfectly into an array without wasting space.
i:2 * i + 12 * i + 2(i - 1) / 2This mapping allows us to navigate the heap without storing extra pointers, which makes the implementation simpler and faster.
Here:
heap array stores the values.size keeps track of how many elements are in the heap.Consider a max heap stored as:
index 0 is 50.2*0+1 (index 1) and 2*0+2 (index 2), which hold 30 and 20.index 1, its children are at 2*1+1 (index 3) and 2*1+2 (index 4), which hold 15 and 10.This is why heaps are implemented using arrays: the index arithmetic maps cleanly to the parent-child relationships of the tree.
The core heap operations are peek, insert, extract, and build-heap.
For the examples we will use a max-heap. The logic for a Min-Heap is identical; you just reverse the comparison operations.
This is the simplest and fastest operation.
index 0.When we add a new element, we must satisfy both heap properties:
Satisfy the Complete Tree property first by adding the new element to the last available spot in the tree, which is the end of the array.
This, however, will almost certainly break the Heap Property. The new element might be larger than its parent. To fix this, we "bubble up" (or "percolate up") the new element.
We compare it to its parent; if it's larger, we swap them. We repeat this process (compare, swap, move up) until the new element finds its rightful place, either by being smaller than its new parent or by becoming the new root.
Here's what it looks like in code:
heap list.i = heap.size() - 1).i > 0):parent = (i - 1) / 2).i to be the parent's index and continue the loop.Time Complexity is O(log n) because the height of a complete binary tree with n nodes is log_2(n). In the worst case, the new element must travel all the way from a leaf to the root. This path is, by definition, the height of the tree.
This is the most common heap operation. It removes and returns the root element. Like insert, this operation must also preserve both heap properties.
Removing the root (index 0) is easy, but it leaves a hole at the top. This breaks the Complete Tree property.
To fix this:
To fix this, we "sift down" (or "heapify down") the new root. We compare it to its children. If it's smaller than either of them, we swap it with the larger of its two children (to ensure the largest value moves up). We repeat this process (compare, swap with largest child, move down) until the element finds its correct position (i.e., it's larger than both of its children, or it becomes a leaf).
Here's how it looks in code:
heap.get(0)) in a variable to return later.heap.set(0, lastElement)).heapifyDown(0) to sift the new root down to its correct position.Time complexity: O(log n). Just like insert, the operation follows a path from the root to a leaf. The length of this path is the tree's height, which is O(log n).
Sometimes, you are given an entire unsorted array and need to convert it into a valid heap.
You could start with an empty heap and call insert() for each of the $n$ elements in the array. Since each insert is O(log n), this would take O(n log n) time. A bottom-up approach runs in O(n).
We know that all leaf nodes (the entire second half of the array) are, by definition, already valid heaps of size 1. They have no children, so the heap property is trivially true.
The real work is with the non-leaf nodes (the first half of the array). If we can ensure that every non-leaf node satisfies the heap property relative to its children, the whole tree will become a valid heap. So, we iterate backwards from the last non-leaf node up to the root (index 0). For each of these nodes, we call heapifyDown(). This "sifts down" the element, allowing larger children to move up and restoring the local heap property. Because we start from the bottom, by the time we heapifyDown a node, we are guaranteed that its children are already valid heap roots themselves.
Here is how it works:
heap list.(heap.size() / 2) - 1.for (int i = ...; i >= 0; i--)).heapifyDown(i).Time Complexity is O(n).
This is a classic analysis. It looks like O(n log n) because we call heapifyDown (an O(log n) operation) n/2 times. But this is a loose upper bound.
h from 0 to log n, of (number of nodes at height h) × (work per node), which is roughly (n / 2^(h+1)) × h. This series converges to O(n).You are doing a lot of cheap work on the many nodes at the bottom and a small amount of expensive work on the few nodes at the top. The total cost is dominated by the linear number of nodes, not the logarithmic height.
In production code and interview solutions, you'll almost always use the language's built-in heap or priority queue instead of writing one from scratch. The implementations below are all backed by binary heaps with the standard complexities: O(log n) push/pop, O(1) peek.
One detail worth noting upfront: defaults differ across languages. Java, Python, and C# default to a min-heap (smallest element at the root). C++ and Rust default to a max-heap. Picking the wrong default is one of the most common bugs in heap-based interview answers.
Java's PriorityQueue<E> is a min-heap by default. To turn it into a max-heap, pass a reverse-order comparator.
Once you're comfortable with the built-in API in your language, most heap-based interview problems become straightforward: identify the priority, push the right elements, and pop in the right order.
10 quizzes