Priority Queues: The Built-in Heap in Each Language
A priority queue is an abstract data type where you always remove the highest-priority element next, rather than the oldest (a queue) or newest (a stack). It is almost always implemented with a binary heap, so the operations cost O(log n) to push and pop and O(1) to peek. Three of our four languages ship one in the standard library; JavaScript does not, so you implement a small heap yourself.
The one thing that trips everyone up: the default direction
Each language’s default is different. Get this wrong and your “min” queue silently returns maxes.
| Language | Built-in | Default order | Top = |
|---|---|---|---|
| C++ | std::priority_queue | max-heap | largest |
| Java | PriorityQueue | min-heap | smallest |
| Python | heapq (functions on a list) | min-heap | smallest |
| JavaScript | none — write your own | up to you | up to you |
For beginners: “Priority” just means which one comes out first. A max-priority-queue serves the biggest value first; a min-priority-queue serves the smallest. Always confirm the default before trusting it.
Push, peek, pop in each language
Here we push a few numbers and pop them in priority order. To make the behaviour identical across languages, each snippet builds a min-priority-queue (smallest comes out first), overriding the C++ default.
#include <queue>
#include <vector>
// Min-heap: greater<int> flips the default max-heap.
std::priority_queue<int, std::vector<int>, std::greater<int>> pq;
pq.push(5); pq.push(1); pq.push(3);
int top = pq.top(); // 1 (peek, O(1))
pq.pop(); // removes 1, O(log n)
// remaining tops would be 3, then 5
import java.util.PriorityQueue;
// PriorityQueue is a min-heap by default.
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.add(5); pq.add(1); pq.add(3);
int top = pq.peek(); // 1 (peek, O(1))
pq.poll(); // removes and returns 1, O(log n)
// remaining peeks would be 3, then 5
// JavaScript has NO built-in heap. Minimal binary min-heap:
class MinHeap {
constructor() { this.a = []; }
size() { return this.a.length; }
peek() { return this.a[0]; }
push(x) {
const a = this.a;
a.push(x);
let i = a.length - 1;
while (i > 0) {
const p = (i - 1) >> 1;
if (a[i] >= a[p]) break;
[a[i], a[p]] = [a[p], a[i]];
i = p;
}
}
pop() {
const a = this.a;
const top = a[0], last = a.pop();
if (a.length) {
a[0] = last;
let i = 0;
const n = a.length;
while (true) {
const l = 2*i + 1, r = 2*i + 2;
let s = i;
if (l < n && a[l] < a[s]) s = l;
if (r < n && a[r] < a[s]) s = r;
if (s === i) break;
[a[i], a[s]] = [a[s], a[i]];
i = s;
}
}
return top;
}
}
const pq = new MinHeap();
pq.push(5); pq.push(1); pq.push(3);
const top = pq.peek(); // 1
pq.pop(); // removes 1
import heapq
# heapq turns a plain list into a min-heap.
pq = []
heapq.heappush(pq, 5)
heapq.heappush(pq, 1)
heapq.heappush(pq, 3)
top = pq[0] # 1 (peek, O(1))
heapq.heappop(pq) # removes and returns 1, O(log n)
# remaining peeks would be 3, then 5
Note for JavaScript: there is no
PriorityQueuein the language or standard runtime. TheMinHeapclass above is the documented, idiomatic approach — the same sift-up/sift-down logic from heap operations. For a max-heap, flip the two comparisons (>=to<=, and<to>).
Making a max-priority-queue
Each language has a clean idiom for the other direction:
#include <queue>
// Default IS a max-heap — largest on top.
std::priority_queue<int> maxpq;
maxpq.push(5); maxpq.push(1); maxpq.push(3);
int top = maxpq.top(); // 5
import java.util.*;
// Pass a reverse comparator to flip to a max-heap.
PriorityQueue<Integer> maxpq = new PriorityQueue<>(Collections.reverseOrder());
maxpq.add(5); maxpq.add(1); maxpq.add(3);
int top = maxpq.peek(); // 5
// Reuse the MinHeap class but store negated values for a max-heap,
// or flip the comparisons inside push/pop. Negation trick:
const maxpq = new MinHeap();
[5, 1, 3].forEach(x => maxpq.push(-x));
const top = -maxpq.peek(); // 5
import heapq
# heapq is min-only; negate values for a max-heap.
maxpq = []
for x in (5, 1, 3):
heapq.heappush(maxpq, -x)
top = -maxpq[0] # 5
Storing more than numbers: keyed priorities
Real priority queues order objects by a key. Use a comparator (C++/Java/JS) or a (priority, item) tuple (Python):
#include <queue>
#include <string>
#include <vector>
struct Task { int priority; std::string name; };
struct ByPriority {
bool operator()(const Task& a, const Task& b) const {
return a.priority > b.priority; // min by priority
}
};
std::priority_queue<Task, std::vector<Task>, ByPriority> pq;
pq.push({2, "email"});
pq.push({1, "alarm"});
std::string next = pq.top().name; // "alarm"
import java.util.*;
record Task(int priority, String name) {}
PriorityQueue<Task> pq =
new PriorityQueue<>(Comparator.comparingInt(Task::priority)); // min by priority
pq.add(new Task(2, "email"));
pq.add(new Task(1, "alarm"));
String next = pq.peek().name(); // "alarm"
// Order tasks by .priority using the MinHeap, storing [priority, name].
// (A comparator-based heap is cleaner in real code; tuples keep it short.)
const pq = new MinHeap2((x) => x[0]); // see note below
pq.push([2, "email"]);
pq.push([1, "alarm"]);
const next = pq.peek()[1]; // "alarm"
// MinHeap2 is MinHeap with comparisons on keyFn(a[i]) vs keyFn(a[p]).
import heapq
# Tuples compare lexicographically: (priority, name).
pq = []
heapq.heappush(pq, (2, "email"))
heapq.heappush(pq, (1, "alarm"))
next_task = pq[0][1] # "alarm"
Going deeper: Standard-library priority queues do not support efficient “decrease-key” (changing an element’s priority in place). The common workaround — used in Dijkstra’s algorithm — is lazy deletion: push the updated entry and skip stale ones when popped.
Next, see the O(n) build and heap sort, or practice on the heap exercises.