Imagine running a busy emergency room where patients arrive continuously with different severity levels. You don’t treat them in arrival order—the most critical patient always goes first. When a doctor becomes free, they instantly know who needs help most urgently. A heap is the data structure that makes this “always know the next best choice” pattern fast and simple.
What is a Heap
A heap is a tree-based data structure maintaining a simple rule: every parent is either greater or smaller than all its children. A min-heap keeps the smallest element at the root—each parent smaller than its children. A max-heap does the opposite—largest at the top, each parent greater than its children. This heap invariant means you can peek at the minimum (or maximum) value in O(1) time. No searching, no sorting—just look at the root.
How a Heap is Stored
Heaps are typically implemented as binary trees, but instead of using pointers, they are stored in arrays for efficiency. A Heap must satisfy some conditions:
- Structure: A heaps should be a complete binary tree, meaning all levels are fully filled except possibly the last, which is filled from left to right.
- Ordering: Any heap’s root must satisfy the heap property (min or max). It is either less than or equal to (min-heap) or greater than or equal to (max-heap) its children.
- Operations: Any operations that grow or shrink the heap must maintain both the structure and ordering properties.
Min Heap
Let’s start with example of array [3, 5, 7, 9, 11, 8, 10], the min-heap start at root with index 0 that contains value 3. Then its left child is at index 1 (value 5) and right child at index 2 (value 7). In min-heap, every parent node is less than or equal to its children.
---
config:
layout: elk
look: handDrawn
---
flowchart TD
A@{shape: circle, label: "Index 0: 3 (root)"}
B@{shape: circle, label: "Index 1: 5 (left child)"}
C@{shape: circle, label: "Index 2: 7 (right child)"}
D@{shape: circle, label: "Index 3: 9 (left child of 5)"}
E@{shape: circle, label: "Index 4: 11 (right child of 5)"}
F@{shape: circle, label: "Index 5: 8 (left child of 7)"}
G@{shape: circle, label: "Index 6: 10 (right child of 7)"}
A --> B
A --> C
B --> D
B --> E
C --> F
C --> G
Max Heap
In the contrast with min-heap, a max-heap example could be represented by the array sorted by descending order [10, 9, 8, 5, 7, 3, 1], where the root at index 0 contains value 10. In max-heap, every parent node is greater than or equal to its children.
---
config:
layout: elk
look: handDrawn
---
flowchart TD
A@{shape: circle, label: "Index 0: 10 (root)"}
B@{shape: circle, label: "Index 1: 9 (left child)"}
C@{shape: circle, label: "Index 2: 8 (right child)"}
D@{shape: circle, label: "Index 3: 5 (left child of 9)"}
E@{shape: circle, label: "Index 4: 7 (right child of 9)"}
F@{shape: circle, label: "Index 5: 3 (left child of 8)"}
G@{shape: circle, label: "Index 6: 1 (right child of 8)"}
A --> B
A --> C
B --> D
B --> E
C --> F
C --> G
Operations and Costs
| Operation | Time Complexity | Description |
|---|---|---|
| Peek | O(1) | Read the root element without modifying the heap. |
| Insert (heapify-up) | O(log n) | Add the new element at the end of the array, then bubble it upward by swapping with its parent until the heap property is restored. |
| Extract (heapify-down) | O(log n) | Remove the root, replace it with the last element, then sink it downward by swapping with the smaller (or larger) child until order is restored. |
| Build-heap | O(n) | Convert an unsorted array into a heap by heapifying from the bottom up. This is faster than inserting elements one by one. |
Implementation Concept
Here’s how the core heap operations look in Python:
class MinHeap:
def __init__(self):
self.heap = []
def peek(self):
return self.heap[0] if self.heap else None
def insert(self, value):
self.heap.append(value)
self._heapify_up(len(self.heap) - 1)
def extract_min(self):
if not self.heap:
return None
if len(self.heap) == 1:
return self.heap.pop()
root = self.heap[0]
self.heap[0] = self.heap.pop()
self._heapify_down(0)
return root
def _heapify_up(self, i):
parent = (i - 1) // 2
if i > 0 and self.heap[i] < self.heap[parent]:
self.heap[i], self.heap[parent] = self.heap[parent], self.heap[i]
self._heapify_up(parent)
def _heapify_down(self, i):
smallest = i
left = 2 * i + 1
right = 2 * i + 2
if left < len(self.heap) and self.heap[left] < self.heap[smallest]:
smallest = left
if right < len(self.heap) and self.heap[right] < self.heap[smallest]:
smallest = right
if smallest != i:
self.heap[i], self.heap[smallest] = self.heap[smallest], self.heap[i]
self._heapify_down(smallest)
The heapify-up process bubbles a value upward by comparing with its parent. The heapify-down process sinks a value downward by comparing with both children and swapping with the smallest. Both follow the tree indices faithfully.
Mini Example: Min-Heap in Action
Start with an empty min-heap. Insert: 5, 3, 8, 1.
- Insert 5: Heap is [5]. Root = 5.
- Insert 3: Heap becomes [5, 3]. Swap 3 with 5. Now [3, 5]. Root = 3.
- Insert 8: Heap becomes [3, 5, 8]. No swaps needed. Root = 3.
- Insert 1: Heap becomes [3, 5, 8, 1]. Swap 1 with 3. Now [1, 5, 8, 3]. Root = 1.
- Extract minimum: Remove 1, replace with 3. Heap becomes [3, 5, 8]. Swap 3 down if needed—no swap required. Root = 3.
Each insert required at most two comparisons. Each extract needed one comparison. This is the efficiency of heapify.
---
config:
layout: elk
look: handDrawn
---
graph TD
subgraph "Step 1: Insert 5"
A1["Array: [5]
Tree:
5"]
end
subgraph "Step 2: Insert 3"
B1["Array: [5,3]
Tree:
5
/
3"]
B2["3 < 5, swap"]
B3["Array: [3,5]
Final Tree:
3
/
5"]
end
subgraph "Step 3: Insert 8"
C1["Array: [3,5,8]
Tree:
3
/ \
5 8"]
C2["8 > 3, no swap
Final Tree unchanged"]
end
subgraph "Step 4: Insert 1"
D1["Array: [3,5,8,1]
Tree:
3
/ \
5 8
/
1"]
D2["1 < 5, swap up"]
D3["Array: [3,1,8,5]
Tree:
3
/ \
1 8
/
5"]
D4["1 < 3, swap up"]
D5["Array: [1,3,8,5]
Final Tree:
1
/ \
3 8
/
5"]
end
subgraph "Step 5: Extract Min"
E1["Remove 1, replace with 5:
Array: [5,3,8]
Tree:
5
/ \
3 8"]
E2["5 > 3, swap down"]
E3["Array: [3,5,8]
Final Tree:
3
/ \
5 8"]
end
A1 --> B1
B3 --> C1
C2 --> D1
D4 --> E1
Why all this matters?
Heaps is the example of a data structure that provides efficient priority management. They underpin priority queues, scheduling algorithms, and graph algorithms like Dijkstra’s shortest path. By ensuring that the highest (or lowest) priority item is always accessible in logarithmic time, heaps enable systems to make optimal decisions quickly without the overhead of full sorting.
In fact, many real-world systems rely on heaps concepts:
- Operating Systems: Task schedulers use heaps to manage process priorities, ensuring high-priority tasks get CPU time first.
- Networking: Routers use heaps to prioritize packets, ensuring time-sensitive data (like VoIP) is transmitted promptly.
- Databases: Query optimizers use heaps to manage execution plans based on cost estimates
When Not to Use a Heap
Heaps excel at “give me the best” but stumble elsewhere:
- Sorted iteration: Heaps do not maintain full sorted order. Iterating over a heap gives no guarantees. If you need sorted output, use a full sort or a balanced binary search tree.
- Order statistics: Finding the median or k-th smallest element repeatedly requires a balanced BST or specialized structures, not a heap.
- Membership checks: Heaps are slow for “does this element exist?” queries. Use a hash set instead.
- One-time batch sorting: If you sort once and never update, a standard sort algorithm beats building and extracting from a heap.
When not to use a Heap and alternatives
| Use Case | Why Not a Heap? | Alternative Data Structure |
|---|---|---|
| Sorted iteration | Heaps do not maintain sorted order. | Balanced Binary Search Tree (BST) |
| Order statistics | Finding median/k-th element is slow. | Balanced BST or specialized structure |
| Membership checks | Slow for existence queries. | Hash Set |
| One-time batch sorting | Standard sort is more efficient. | Quick Sort or Merge Sort |
Closing Thoughts
Just like the emergency room nurse who always knows which patient needs attention next, a heap ensures your program can instantly access the most important item without wasting time sorting the entire queue. Whether you’re scheduling tasks, routing network packets, or processing high-frequency trading signals, heaps give you the best next choice in logarithmic time. That efficiency is why they appear in nearly every high-performance system.
Questions
Test your understanding of heaps with these knowledge check questions:
