Hello! Welcome back to our module on Heaps and Priority Queues.
In the last lesson, we dug into the mechanics of heaps, understanding their core properties and implementing the essential _sift_up and _sift_down helper methods. We established that these operations allow us to maintain the heap property in time.
Today, we'll put that foundation to use. We are going to implement a Priority Queue, an abstract data structure that is fundamental to many algorithms and a common feature in technical interviews. By the end of this lesson, you will see how a heap provides a highly efficient underlying structure for a priority queue.
1. What is a Priority Queue?
A standard queue operates on a "first-in, first-out" (FIFO) principle. A Priority Queue (PQ) is different: it's an abstract data type where each element has an associated "priority." When you extract an element, you always get the one with the highest priority, regardless of when it was added.
- In a min-priority queue, the element with the lowest value has the highest priority.
- In a max-priority queue, the element with the highest value has the highest priority.
This concept appears everywhere:
- Operating Systems: Scheduling processes based on their priority level.
- Networking: Routing data packets that have urgent flags.
- AI: In algorithms like A* search, a priority queue is used to explore the most promising paths first.

The main operations of a priority queue are:
- Insert/Enqueue: Add a new element to the queue.
- Extract-Highest-Priority/Dequeue: Remove and return the element with the highest priority.
- Peek: Look at the highest-priority element without removing it.
The critical question for an interview is: what's the most efficient way to build this?
2. Why a Heap is the Ideal Implementation
Let's consider other data structures you know. How would they perform as a priority queue?
- Unsorted Array/List: Insertion is . But to find the highest-priority item, you must search the entire list, which takes time.
- Sorted Array/List: Finding the highest-priority item is (it's at one of the ends). However, inserting an element while keeping the array sorted requires shifting elements, which takes time.
Neither of these is ideal. We want our operations to be faster. This is where heaps shine. To understand the motivation better, let's watch a video that frames this problem perfectly.
Introduction to Heap Data Structure + Priority Queue + Heapsort Tutorial
This video from Kunal Kushwaha does an excellent job of explaining why we need heaps for this problem. He contrasts it with other data structures to show the performance trade-offs.
Watch the segment from 02:00 to 10:15. Focus on the core argument: how can we get the highest/lowest priority item in O(1) time while keeping insertions efficient? This section sets up the exact problem that heaps solve.
As the video argues, a heap provides the perfect balance:
- Peek: , because the highest-priority element is always the root.
- Insert: .
- Extract: .
This is a massive improvement over the complexities of the array-based approaches.
3. Implementing a Priority Queue with Our Heap
Let's now complete the MinHeap class we started in the previous lesson to make it a fully functional min-priority queue. We'll add the public-facing push (insert) and pop (extract-min) methods that use the _sift_up and _sift_down helpers you've already learned.
# Building on the class from the previous lesson
class MinPriorityQueue:
def __init__(self):
# The heap is stored as a list (dynamic array)
self.heap = []
def push(self, val):
"""Adds an element to the priority queue. O(log N)"""
# 1. Add the element to the end to maintain the 'complete tree' shape.
self.heap.append(val)
# 2. Sift it up to restore the heap property.
self._sift_up(len(self.heap) - 1)
def pop(self):
"""Removes and returns the element with the highest priority (the minimum). O(log N)"""
if not self.heap:
return None
# 1. The root is the min element we need to return.
min_val = self.heap[0]
# 2. To fill the hole at the root, move the last element there.
last_val = self.heap.pop() # Removes and returns the last element
# If there are still elements left after popping...
if self.heap:
self.heap[0] = last_val
# 3. Sift the new root down to restore the heap property.
self._sift_down(0)
return min_val
def peek(self):
"""Returns the smallest element without removing it. O(1)"""
return self.heap[0] if self.heap else None
# --- Helper methods from the previous lesson ---
def _parent_index(self, i):
return (i - 1) // 2
def _sift_up(self, i):
# Iterative implementation is common in interviews
parent_idx = self._parent_index(i)
while i > 0 and self.heap[i] < self.heap[parent_idx]:
self.heap[i], self.heap[parent_idx] = self.heap[parent_idx], self.heap[i]
i = parent_idx
parent_idx = self._parent_index(i)
def _sift_down(self, i):
while True:
smallest = i
left_child_idx = 2 * i + 1
right_child_idx = 2 * i + 2
if left_child_idx < len(self.heap) and self.heap[left_child_idx] < self.heap[smallest]:
smallest = left_child_idx
if right_child_idx < len(self.heap) and self.heap[right_child_idx] < self.heap[smallest]:
smallest = right_child_idx
if smallest != i:
self.heap[i], self.heap[smallest] = self.heap[smallest], self.heap[i]
i = smallest
else:
break # The heap property is restored for this subtree
This class is now a complete implementation of a min-priority queue. To see a slightly different but equally valid implementation and review the core logic, you can study the following resource.
Heaps and Priority Queues: A Complete Guide
This article provides another clean, from-scratch implementation of a Min-Heap in Python. Seeing the same logic in a different presentation can help solidify your understanding.
Read the section titled 'Building Your Own Heap: A Min-Heap Implementation from Scratch'. Compare its push and pop methods with the ones we just built. Notice how they both rely on heapify-up (sift-up) and heapify-down (sift-down) to function.
Test your understanding!
You are given an empty MaxPriorityQueue and the following sequence of operations: push(10), push(20), push(5), pop(), push(15), pop(). What is the value returned by the second pop() operation?
Show answer
Let's trace the state of the max-heap (represented as an array):
push(10):[10]push(20):[10, 20]-> Sift-up20->[20, 10]push(5):[20, 10, 5](no sift-up needed)pop():- The max value
20is to be returned. - The last element
5moves to the root:[5, 10]. - Sift-down
5: Swap with larger child10->[10, 5]. - Returns
20. Heap is now[10, 5].
- The max value
push(15):[10, 5, 15]-> Sift-up15->[15, 5, 10]pop():- The max value
15is to be returned. - The last element
10moves to the root:[10, 5]. - Sift-down
10: It's already larger than its child5. No swap needed. - Returns
15.
- The max value
The value returned by the second pop() operation is 15.
4. Priority Queues in Standard Libraries
While knowing how to build a PQ from scratch is vital for demonstrating deep understanding in an interview, you'll almost always use a built-in library for actual problem-solving.
Python: The heapq Module
Python's standard library provides the heapq module, which implements a min-heap. You use it directly on a standard Python list.
A key interview trick is to implement a max-heap using heapq. Since heapq is a min-heap, it always keeps the smallest element at the top. To simulate a max-heap, you can simply insert the negation of your numbers. The smallest negative number will correspond to the largest positive number.
The following resource gives a practical guide to using heapq for both min- and max-priority queues.
How to Use a Priority Queue in Python
This guide from DigitalOcean covers the practical use of Python's heapq module. It's essential for interview preparation.
Read the sections 'How to Implement a Priority Queue Using heapq', 'What is a Min-Heap vs Max-Heap?', and 'How to Implement a Max-Heap using heapq?'. Pay close attention to the trick of using negative values to simulate a max-heap.
Java: The PriorityQueue Class
For your Java proficiency, it's good to know that Java has a PriorityQueue class built-in.

By default, it's a min-priority queue. To make it a max-priority queue, you provide a Comparator that reverses the natural ordering. For example: new PriorityQueue<>(Collections.reverseOrder()); or new PriorityQueue<>((a, b) -> b - a);.
Conclusion
You've successfully connected the low-level heap mechanics to the high-level concept of a priority queue. This is a critical link to understand for solving a wide range of algorithmic problems.
Key Takeaways:
- A Priority Queue is an abstract data type that serves elements based on priority, not insertion order.
- A heap is the most efficient data structure for implementing a priority queue, offering for insertions and extractions, and for peeking.
- The heap's
sift-upandsift-downoperations are the engines that power the priority queue'spushandpopmethods. - For interviews, you should be familiar with both the from-scratch implementation and the standard library versions like Python's
heapqand Java'sPriorityQueue.
In our next lesson, we will apply what we've learned to a classic and very common interview pattern: solving "Top K" element problems using heaps. You'll see just how powerful this data structure can be.