Hello! Welcome to the first lesson in our module on Heaps and Priority Queues.
In the previous module, we delved into backtracking, a powerful algorithmic technique for solving complex search and constraint satisfaction problems like N-Queens. We are now shifting gears from exhaustive search algorithms to a highly efficient data structure that is a cornerstone of competitive programming: the heap.
Heaps are fundamental for any problem that requires repeatedly finding and extracting the smallest or largest element from a collection. This lesson will introduce you to the concept of a heap and how to perform the most essential heap operations using Python's built-in heapq module. By the end of this lesson, you will be able to perform heap operations (push, pop, and heapify) using Python's heapq module.
1. What is a Heap?
At a high level, a heap is a specialized tree-based data structure. Its primary feature is that it satisfies the heap property. For a min-heap, which is the default in Python, this property ensures that the parent node is always smaller than or equal to its child nodes. This means the smallest element in the entire collection is always at the root of the tree, making it instantly accessible.
Heaps are the most common way to implement an abstract data type called a Priority Queue, which you might have heard of.
The Python heapq Module: Using Heaps and Priority Queues
To clarify the relationship between these two terms, read this short section from the Real Python article 'The Python heapq Module'. It explains why a Priority Queue is an abstract concept and a Heap is its concrete implementation.
Read the section titled 'What Are Heaps?'. Focus on the distinction between an abstract data structure (Priority Queue) and a concrete one (Heap), and understand the min-heap convention used by Python.
From Tree to List: The Python Implementation
While we visualize heaps as trees, Python's heapq module doesn't use complex tree node objects. Instead, it uses a simple Python list. This is possible because a heap is a complete binary tree, which can be represented compactly in an array or list.
The relationship between a parent node at index k and its children is defined by simple formulas:
- Left Child:
2*k + 1 - Right Child:
2*k + 2 - Parent:
(k - 1) // 2(using integer division)

The following video provides an excellent visual introduction to what a heap is and how the main operations work conceptually.
Heaps & Priority Queues - Heapify, Heap Sort, Heapq Library - DSA Course in Python Lecture 9
Watch this segment from Greg Hogg's DSA course to see how heaps are structured and how push and pop operations conceptually maintain the heap property by 'sifting' elements up or down the tree.
Watch from the beginning to 06:08. Pay attention to how the array representation maps to the tree and the logic behind heap pop (extract-min) and heap push (insert). Don't worry about the time complexity details just yet; we'll focus on the 'how'.
2. Core Operations with the heapq Module
Python's heapq module provides functions that operate directly on lists. You don't create a Heap object; you apply heapq functions to your lists. Let's explore the three fundamental operations.
a) heapify: Creating a Heap from a List
If you have an existing list of elements that you want to turn into a heap, you can use heapq.heapify(). This function rearranges the elements of the list in-place to satisfy the heap property.
A key point for competitive programming is that heapify is a highly optimized operation with a time complexity of O(N), where N is the number of elements in the list. This is more efficient than inserting N elements one by one.
This section from GeeksforGeeks provides a concise explanation and a simple code example for using heapify.
Read the section 'Creating a Heap Queue', focusing on the heapify() function.
Here's how you use it:
import heapq
# An unsorted list
my_list = [5, 7, 9, 1, 3, 4]
# Convert the list into a min-heap in-place
heapq.heapify(my_list)
print("Heapified list:", my_list)
# Expected Output (the first element is the smallest):
# Heapified list: [1, 3, 4, 7, 5, 9]
# Note: It's not a sorted list, but it satisfies the heap property.
b) heappush: Adding an Element
To add a new element to an existing heap while maintaining the heap property, use heapq.heappush(). This function adds the element and ensures the heap structure is preserved. This operation has a time complexity of O(log N).
Heapq Module And Priority Queue | Binary Heap | Python Tutorials
Watch this short clip to see heappush in action.
Watch from 00:56 to 02:51. Observe how pushing a smaller element restructures the list to keep the smallest value at the front.
Here's a code example:
import heapq
my_heap = [1, 3, 4, 7, 5, 9] # This is already a heap
# Push the value 2 into the heap
heapq.heappush(my_heap, 2)
print("Heap after push:", my_heap)
# Expected Output: [1, 3, 2, 7, 5, 9, 4]
# The internal order changes, but my_heap[0] is still the minimum.
# Push an even smaller value, 0
heapq.heappush(my_heap, 0)
print("Heap after pushing 0:", my_heap)
# Expected Output: [0, 1, 2, 3, 5, 9, 4, 7]
c) heappop: Removing the Smallest Element
To remove and get the smallest element from the heap, use heapq.heappop(). It will always remove the element at index 0 and then rearrange the heap to maintain the property. This is also an O(log N) operation.
Heapq Module And Priority Queue | Binary Heap | Python Tutorials
This clip demonstrates how heappop works.
Watch from 02:51 to 04:28 to see how heappop removes and returns the smallest element.
Let's continue with our example:
import heapq
my_heap = [0, 1, 2, 3, 5, 9, 4, 7] # Our heap from before
# Pop the smallest element
smallest = heapq.heappop(my_heap)
print("Popped:", smallest)
print("Heap after pop:", my_heap)
# Expected Output:
# Popped: 0
# Heap after pop: [1, 3, 2, 7, 5, 9, 4]
# Pop again
next_smallest = heapq.heappop(my_heap)
print("Popped:", next_smallest)
print("Heap after pop:", my_heap)
# Expected Output:
# Popped: 1
# Heap after pop: [2, 3, 4, 7, 5, 9]
Test your understanding!
You are given the list data = [10, 5, 20]. What will the list look like after the following two operations are performed in sequence?
heapq.heappush(data, 2)smallest = heapq.heappop(data)
Try to trace the steps in your head before checking the answer.
Show answer
- Initial list:
data = [10, 5, 20] heapq.heappush(data, 2): The element2is added. Since it's the smallest, it will effectively become the new root after sifting operations. The heap might look something like[2, 5, 20, 10]. The exact internal arrangement can be complex, but what's guaranteed is that2will be at index 0.smallest = heapq.heappop(data): The smallest element,2, is removed. The heap restructures itself.smallestwill be2. The remaining heap will have5as its new smallest element at the root. A possible state fordatawould be[5, 10, 20].
3. Essential Heap Techniques
Accessing the Smallest Element (Peeking)
Often, you just need to see the smallest element without removing it. This is called "peeking". Since the heap is just a list with the smallest element at the front, you can do this in O(1) time by simply accessing my_heap[0].
my_heap = [2, 3, 4, 7, 5, 9]
print("Smallest element is:", my_heap[0]) # Prints 2
Simulating a Max-Heap
Python's heapq is a min-heap. What if you need the largest element instead? The standard trick is to insert the negative of each number. The smallest negative number corresponds to the largest positive number. When you pop an element, you just negate it again to get the original value.
Heaps & Priority Queues - Heapify, Heap Sort, Heapq Library - DSA Course in Python Lecture 9
Greg Hogg explains this common and crucial technique for implementing a max-heap using Python's min-heap library.
Watch from 18:26 to 20:34. This section clearly demonstrates how to use negative numbers to simulate a max-heap for pushing and popping.
Here is a code example implementing this trick:
import heapq
data = [1, 5, 2, 8, 3]
max_heap = []
# To create a max-heap, push the negative of each value
for item in data:
heapq.heappush(max_heap, -item)
# Now, max_heap[0] is -8, the negative of the largest element.
print("Max-heap (stored as negatives):", max_heap)
# To pop the largest element, pop from the max_heap and negate the result
largest_element = -heapq.heappop(max_heap)
print("Largest element popped:", largest_element) # Should be 8
print("Max-heap after pop:", max_heap)
Conclusion
Great work! You've just learned the fundamentals of one of the most useful data structures in competitive programming. It's a simple yet powerful tool for optimizing any algorithm that involves priorities or ordering.
Key Takeaways:
- A heap is a tree-based data structure (implemented as a list in Python) that satisfies the heap property, making it efficient to find the minimum element.
- Python's
heapqmodule provides the core functions:heapq.heapify(list): Converts a list to a heap in O(N) time.heapq.heappush(heap, item): Adds an item in O(log N) time.heapq.heappop(heap): Removes and returns the smallest item in O(log N) time.
- Accessing the smallest item without removal (peeking) is an O(1) operation:
heap[0]. - A max-heap can be simulated by storing the negative of the values in a min-heap.
Up Next:
In the next lesson, we will build directly on what you've learned today. We'll officially define and use the heap as a Priority Queue to solve common problems that appear frequently in coding interviews and competitions.