AlgoMaster Logo

Introduction to Heaps

High Priority11 min readUpdated May 30, 2026
Listen to this chapter
Unlock Audio

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:

  • Priority-based task scheduling in operating systems.
  • Shortest-path algorithms like Dijkstra.

This chapter covers:

  • What a heap is and how it works
  • Different types of heaps
  • How to implement a heap in code
  • Core heap operations and their complexities

What is a Heap?

A heap is a binary tree optimized for fast access to the highest-priority element.

503010204015

It follows two key rules:

1. Complete Binary Tree

  • A heap is always a complete binary tree.
  • Every level of the tree is completely filled, except possibly the last.
  • In the last level, nodes are filled from left to right with no gaps.

This is what keeps the tree balanced and ensures operations like insertion and deletion are efficient.

2. Heap Property

Every heap must also satisfy the heap property, which comes in two flavors:

Max Heap
503010204015
  • Every parent node is greater than or equal to its children.
  • The root node is always the largest element in the heap.
  • Used when you need fast access to the maximum value.
Min Heap
5103040152520
  • Every parent node is smaller than or equal to its children.
  • The root node is always the smallest element in the heap.
  • Useful when you want quick access to the minimum value.

Representing a Heap

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.

  • For a node at index i:
    • The left child is at 2 * i + 1
    • The right child is at 2 * i + 2
    • The parent is at (i - 1) / 2

This mapping allows us to navigate the heap without storing extra pointers, which makes the implementation simpler and faster.

Here:

  • The heap array stores the values.
  • size keeps track of how many elements are in the heap.
  • The three helper functions provide constant-time navigation between parent and child nodes.

Quick Example

Consider a max heap stored as:

5030151020816
  • The root at index 0 is 50.
  • Its children sit at 2*0+1 (index 1) and 2*0+2 (index 2), which hold 30 and 20.
  • For 30 at 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.

Common Heap Operations

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.

1. Peek

This is the simplest and fastest operation.

  • The peek operation returns the root element without removing it.
  • In a max-heap, it's the max value, and it's always at the root of the tree, which corresponds to the first element in our heap array at index 0.
  • The time complexity is O(1) since it's a direct lookup.

2. Insert

When we add a new element, we must satisfy both heap properties:

  1. Complete tree property: The tree must be filled from left to right, with no gaps.
  2. Heap property: Every parent must be greater than or equal to its children (for a max-heap).

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:

  1. Add the new value to the end of the heap list.
  2. Get the index of this new element (i = heap.size() - 1).
  3. While the element is not the root (i > 0):
    1. Find its parent's index (parent = (i - 1) / 2).
    2. If the element is greater than its parent, swap them.
    3. Update i to be the parent's index and continue the loop.
    4. If the element is less than or equal to its parent, the heap property is restored. Stop 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.

3. Extract Max: Remove the Top Element

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:

  1. We take the last element from the array (the right-most leaf) and move it into the root's empty spot.
  2. This restores the Complete Tree property immediately. We no longer have a hole.
  3. But now, the Heap Property is almost certainly broken. The element we just moved to the root is likely smaller than its new children.

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:

  1. If the heap is empty, throw an exception.
  2. Store the root element (heap.get(0)) in a variable to return later.
  3. Remove the last element from the heap and store it.
  4. If the heap is now empty (we just removed the only element), return the stored root.
  5. If not, place the (previously last) element at the root (heap.set(0, lastElement)).
  6. Call 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).

4. Build Heap (Heapify an Array)

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:

  1. Copy all elements from the input array into our heap list.
  2. Find the index of the last non-leaf node. This is (heap.size() / 2) - 1.
  3. Iterate backwards from this index down to 0 (for (int i = ...; i >= 0; i--)).
  4. In each iteration, call heapifyDown(i).
  5. When the loop finishes, the entire array is a valid heap.

Time Complexity is O(n).

Why O(n) and not O(n log 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.

  • A tighter analysis shows that most of the work is cheap.
  • The nodes at the bottom (e.g., height 1) only sift down 1 level. There are n/4 such nodes.
  • The nodes at height 2 only sift down 2 levels. There are n/8 such nodes.
  • The root (height log n) sifts down log n levels, but there is only 1 root.
  • The total work is bounded by the sum, across all heights 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.

Built-in Libraries

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.

Quiz

Introduction Quiz

10 quizzes