The Binary Heap and the Priority Queue

Matt George

Fractional CTO · Founder of Injuria.ai

•20 min read•

The Binary Heap and the Priority Queue

A priority queue is an abstract data type that operates similarly to a standard queue or stack, but with a fundamental distinction: every element is associated with a specific priority value.[28] Instead of serving elements based strictly on their insertion order, a priority queue always serves the element with the highest priority first. This abstraction is foundational in computer science, enabling the efficient execution of scheduling systems, graph algorithms, and discrete event simulations.[29] To implement this abstraction efficiently, computer scientists rely on specific concrete data structures. Before the invention of specialized tree structures, programmers faced a strict dichotomy between arrays and linked lists. A simple unsorted array offers fast insertions but slow retrievals, because finding the highest priority element requires scanning the entire collection. A sorted array offers fast retrievals but slow insertions, because adding a new element requires shifting all subsequent elements in memory. The priority queue requires a balanced mathematical approach to ensure both operations are fast.

The binary heap is the most common and historically significant data structure used to implement a priority queue.[30] It is a complete binary tree that satisfies a strict hierarchical rule known as the heap property. This structural guarantee ensures that the highest priority element is always located at the root of the tree, allowing for immediate retrieval. The binary heap is typically implemented as an implicit data structure within a standard contiguous array. By using simple arithmetic operations on array indices, the structure avoids the memory overhead of storing explicit pointers to child and parent nodes, making it highly efficient in terms of space.

The formalization of the binary heap provided a guaranteed logarithmic time complexity for both insertion and extraction operations. This mathematical efficiency bridged a major gap in algorithm design, directly enabling the creation of the Heapsort algorithm and allowing for the practical implementation of network optimization algorithms. Today, the binary heap and its theoretical descendants form the backbone of timer management in operating systems, task schedulers, and the standard libraries of nearly all modern programming languages.

The Priority Queue Abstraction

The priority queue is defined by its supported operations rather than its underlying memory layout. In a standard First-In-First-Out queue, elements are extracted in the exact order they arrived. In a Last-In-First-Out stack, the most recently added element is extracted first. A priority queue discards arrival time as the primary sorting metric, instead requiring that each element be evaluated by a comparator function. This comparator function must establish a strict weak ordering over the elements, ensuring that the priority relationship is irreflexive, asymmetric, and transitive.

The core operations of a priority queue include:

  • insert(item, priority): Adds a new item to the collection with an associated priority.
  • find_minimum(): Returns the item with the highest priority (often defined as the lowest numerical value) without removing it from the collection.
  • extract_minimum(): Returns and removes the item with the highest priority.

Early computing systems implemented these operations using naive data structures, which resulted in severe performance bottlenecks. Using an unsorted linked list allows the insert operation to complete in O(1) constant time, but the extract_minimum operation requires an O(n) linear scan of the entire list to locate the highest priority item. Conversely, maintaining a sorted linked list allows extract_minimum to complete in O(1) time by simply removing the head node, but the insert operation requires an O(n) traversal to find the correct insertion point.

Theoretical computer science dictates a strict mathematical lower bound for priority queue operations. Because a priority queue can be used to sort a collection of n elements by inserting them all and then extracting them all, the time complexity of either insertion or extraction must be at least O(log n) in a comparison-based model. If both operations could be completed in O(1) time, an algorithm could sort n elements in O(n) time, which violates the proven Omega(n log n) lower bound for comparison sorting. The priority queue abstraction inherently requires logarithmic time for at least one of its primary operations.

Advanced implementations of the priority queue also support operations that are necessary for complex graph algorithms. The decrease_key(item, new_priority) operation allows the system to update the priority of an existing element, moving it closer to the front of the queue. The merge(queue1, queue2) operation combines two separate priority queues into a single valid queue. The efficiency of these operations dictates which concrete data structure is chosen for a given application Introduction to Algorithms, Fourth Edition[1].[31]

The Binary Heap Mechanism

The binary heap implements the priority queue abstraction by maintaining two strict invariants: a shape property and a heap property.

The shape property dictates that the binary heap must be a complete binary tree. In a complete binary tree, every level is fully populated except possibly the last level, which is filled continuously from left to right. This structural rule guarantees that a tree containing n nodes has a height of exactly floor(log2(n)). Because the height is strictly logarithmic, any operation that traverses a path from the root to a leaf will perform at most a logarithmic number of comparisons.

The heap property dictates the ordering of the nodes. In a min-heap, the priority function requires that smaller values represent higher priorities. The heap property states that the key of any parent node must be less than or equal to the keys of its children. Consequently, the absolute minimum element is always located at the root. Conversely, a max-heap requires that the key of any parent node is greater than or equal to the keys of its children, placing the maximum element at the root.[31]

To avoid the overhead of storing memory pointers, a binary heap is almost always flattened into a standard array. The mathematical mapping between tree nodes and array indices depends on whether the array uses 0-based or 1-based indexing. In classical algorithm textbooks, 1-based indexing is often preferred because it simplifies the arithmetic. For a node at index i in a 1-based array, its left child is at 2i, its right child is at 2i + 1, and its parent is at floor(i / 2). In modern programming languages, which predominantly use 0-based indexing, the root is placed at index 0. For any node located at index i, its structural relatives are found using modified arithmetic:

  • The left child is located at index 2i + 1.
  • The right child is located at index 2i + 2.
  • The parent is located at index floor((i - 1) / 2).

These arithmetic operations are highly efficient on modern processors because they can be translated directly into bitwise instructions. Multiplying by two is equivalent to a bitwise left shift, and dividing by two is equivalent to a bitwise right shift. For a node at index i in a 0-based array, the left child is located at (i << 1) | 1, the right child is located at (i << 1) + 2, and the parent is located at (i - 1) >> 1. These bitwise shifts execute in a single CPU cycle, bypassing the slower multiplication and division circuits of the arithmetic logic unit.

When a new element is added via the insert operation, it is placed at the end of the array to maintain the complete tree shape property. However, this new element might violate the heap property. To restore the invariant, the algorithm performs a "sift-up" operation. The new element is compared to its parent. If the new element is smaller in a min-heap, the two elements are swapped. This comparison and swapping process repeats up the tree until the element is greater than or equal to its parent, or until it reaches the root. The time complexity of insertion is O(log n) in the worst case.[32]

The extract_minimum operation begins by reading the element at the root. To remove it while maintaining the shape property, the last element in the array is moved to the root position, and the array size is reduced by one. This new root almost certainly violates the heap property. The algorithm then performs a "sift-down" operation. The new root is compared with its children. If it is larger than either child, it is swapped with the smallest of the two children. This process repeats down the tree until the element is smaller than both of its children or it becomes a leaf node. The time complexity of extraction is O(log n) in the worst case.[33]

function sift_down(array, index, heap_size):
    while (2 * index) + 1 < heap_size:
        left_child = (2 * index) + 1
        right_child = (2 * index) + 2
        smallest = index
        
        if array[left_child] < array[smallest]:
            smallest = left_child
            
        if right_child < heap_size and array[right_child] < array[smallest]:
            smallest = right_child
            
        if smallest == index:
            break
            
        swap(array[index], array[smallest])
        index = smallest

Building a binary heap from an unsorted array of n elements can be done by inserting elements one by one, which takes O(n log n) time. However, a more efficient bottom-up construction algorithm, known as heapify, builds the heap in O(n) time. The algorithm starts at the last non-leaf node and iterates backwards to the root, calling sift_down on each node. The mathematical efficiency of this approach derives from the fact that the majority of nodes in a complete binary tree reside at the bottom levels and therefore travel very short distances during the sift-down process. Specifically, an n-element heap has at most ceil(n / 2^(h+1)) nodes at height h. The work done for each node is proportional to its height. The total work is the sum over all heights h of h multiplied by ceil(n / 2^(h+1)). Factoring out n, the infinite series sum of h / 2^(h+1) converges to exactly 1. Therefore, the total work is bounded by a strict O(n) time complexity The Art of Computer Programming, Volume 3[2].

Discovery and History

The binary heap was invented in 1964 by J.W.J. Williams. Williams introduced the data structure in the journal Communications of the ACM as the foundation for a new sorting algorithm he named Heapsort Algorithm 232: Heapsort[3]. During the early 1960s, computer scientists were actively searching for sorting algorithms that could operate efficiently within the severely constrained memory of early computers. Mainframe memory was measured in kilobytes, making auxiliary arrays prohibitively expensive. Quicksort, published by Tony Hoare in 1961, was fast on average but suffered from an O(n^2) worst-case execution time, which could cause unacceptable delays in batch processing systems. Mergesort guaranteed O(n log n) time but required O(n) auxiliary memory to hold the merged arrays. Williams designed the binary heap to solve this exact problem. By organizing the unsorted array into a max-heap in place, and then repeatedly extracting the maximum element to the end of the array, Heapsort achieved a guaranteed O(n log n) worst-case time complexity while requiring only O(1) auxiliary space.

Later in 1964, Robert W. Floyd published a significant refinement to Williams's invention. In his paper detailing the Treesort 3 algorithm, Floyd introduced the linear-time O(n) bottom-up heapify method Algorithm 245: Treesort 3[4]. Floyd recognized that building the initial heap by repeatedly sifting elements up was mathematically suboptimal. By sifting elements down from the bottom of the tree to the top, Floyd reduced the initial heap construction phase from O(n log n) to strictly O(n), cementing the modern implementation of the binary heap.

While the concept of using tree structures for sorting existed prior to 1964, such as the tournament trees used by Kenneth E. Iverson in the development of the APL programming language, Williams and Floyd formalized the strict heap property and the elegant array-based implicit tree representation. Their work established the binary heap as a fundamental primitive in computer science.

Subsequent research refined the theoretical understanding of the binary heap. In 1987, Svante Carlsson published a detailed mathematical analysis of the average-case performance of Heapsort, proving that the algorithm requires fewer comparisons on average than its worst-case bound suggests Average-case results on heapsort[5]. In 1993, Ingo Wegener analyzed a bottom-up variant of the sift_down operation that bypassed one of the two comparisons typically required at each level of the tree, making Heapsort highly competitive with Quicksort in terms of raw comparison counts BOTTOM-UP-HEAPSORT, a new variant of HEAPSORT beating, on an average, QUICKSORT[6].

Important Variants

The standard binary heap provides an optimal balance of time and space for basic priority queue operations, but specific algorithms require different performance guarantees, leading to the development of several important variants.[34]

D-ary Heaps: A d-ary heap generalizes the binary heap by allowing each node to have d children instead of two. This modification reduces the overall height of the tree to log_d(n), which makes the insert operation faster because the sift-up path is shorter. However, the extract_minimum operation becomes slightly slower because the sift-down process must compare d children at each level to find the smallest one. D-ary heaps are frequently used to optimize CPU cache performance. By aligning the d children to fit exactly within a modern hardware cache line, the data structure minimizes memory latency.

Leftist Trees: Invented by Clark Allan Crane in 1972, the leftist tree is a pointer-based priority queue that supports merging two heaps in O(log n) time. Unlike a binary heap, a leftist tree does not enforce the complete binary tree shape property. Instead, it maintains a null path length for each node, defined as the shortest distance from that node to a descendant that does not have two children. The leftist property requires that the null path length of the left child is always greater than or equal to the null path length of the right child. This biases the tree heavily to the left, ensuring that the right path is always short. The merge algorithm only traverses the rightmost paths, guaranteeing logarithmic execution time.

Min-Max Heaps: The standard binary heap only allows fast access to either the minimum or the maximum element, but not both. In 1986, Michael D. Atkinson, Jörg-Rüdiger Sack, Nicola Santoro, and Thomas Strothotte introduced the min-max heap to solve this limitation Min-max heaps and generalized priority queues[7]. A min-max heap is a complete binary tree where the levels alternate between min levels and max levels. The root is on a min level, its children are on a max level, its grandchildren are on a min level, and so forth. This alternating structure allows both extract_minimum and extract_maximum to complete in O(log n) time while maintaining the implicit array representation.

Binomial Heaps: A major limitation of the standard binary heap is that merging two separate heaps requires O(n) time. Jean Vuillemin solved this problem in 1978 by introducing the binomial heap A data structure for manipulating priority queues[8].[32] Instead of a single tree, a binomial heap represents the priority queue as a forest of binomial trees. A binomial tree of order k has 2^k nodes and height k. It is constructed recursively by linking two binomial trees of order k-1, where the root of one becomes the leftmost child of the root of the other. A binomial heap is a collection of these trees where no two trees have the same order, mirroring the binary representation of the number of elements. Merging two binomial heaps is analogous to adding two binary numbers, allowing the merge operation to complete in O(log n) time.

Fibonacci Heaps: Developed by Michael L. Fredman and Robert E. Tarjan in 1984 and formally published in 1987, the Fibonacci heap was designed to optimize the decrease_key operation Fibonacci heaps and their uses in improved network optimization algorithms[9]. The structure uses a lazy evaluation strategy. Instead of restructuring the tree immediately when an element is inserted or its priority is decreased, the Fibonacci heap simply links the new or modified node to a root list. The expensive work of consolidating the trees is delayed until an extract_minimum operation is called. This lazy approach yields an amortized time complexity of O(1) for both insert and decrease_key. This O(1) bound mathematically reduces the time complexity of Dijkstra's algorithm and Prim's algorithm on dense graphs.

Pairing Heaps: While Fibonacci heaps offer excellent theoretical bounds, their complex pointer manipulation results in high constant-factor overhead in practice. To address this, Fredman, Sedgewick, Sleator, and Tarjan introduced the pairing heap in 1986 The pairing heap: a new form of self-adjusting heap[10]. The pairing heap is a self-adjusting multi-way tree that is much simpler to implement. When the minimum element is extracted, the remaining orphaned subtrees are merged in distinct passes. While its theoretical bounds are slightly weaker than those of the Fibonacci heap, the pairing heap consistently outperforms it in real-world benchmarks.

Skew Heaps: Introduced by Daniel Sleator and Robert Tarjan in 1986, skew heaps are self-adjusting binary trees that support O(log n) amortized merging Self-adjusting heaps[11]. Unlike leftist trees, skew heaps do not store any structural balance information in the nodes. Instead, they unconditionally swap the left and right children during every merge operation, which naturally keeps the tree balanced in an amortized sense.

Brodal Queues: In 1996, Gerth Stølting Brodal introduced a priority queue that achieves the optimal worst-case time bounds for all operations without relying on amortization Worst-case efficient priority queues[12]. The Brodal queue supports insertion, finding the minimum, decreasing a key, and merging in strict O(1) time, and extracting the minimum in strict O(log n) time. While theoretically optimal, the data structure is highly complex and carries large constant factors, making it primarily a theoretical milestone rather than a practical tool.

Real-World Applications

The binary heap and its variants are deeply embedded in modern software infrastructure, serving as the default mechanism for managing ordered events and dynamic datasets.

Language Standard Libraries: Almost every major programming language provides a priority queue backed by a binary heap. Python includes the heapq module, which provides functions to maintain a heap within a standard list heapq, Heap queue algorithm[13]. Java provides the PriorityQueue class in its utility package Class PriorityQueue[14]. C++ includes std::priority_queue in the Standard Template Library, which defaults to using a vector as the underlying container std::priority_queue[15]. Rust offers the BinaryHeap struct in its collections module Struct std::collections::BinaryHeap[16]. Go provides the container/heap package to apply heap semantics to any custom collection Package heap[17]. The .NET framework includes the PriorityQueue class for C# developers PriorityQueue Class[18].

Graph Algorithms: Priority queues are essential for executing graph search algorithms efficiently. Dijkstra's algorithm for finding the shortest path in a graph relies on a priority queue to repeatedly select the unvisited node with the lowest tentative distance from the source A note on two problems in connexion with graphs[19]. Using a standard binary heap, Dijkstra's algorithm runs in O((V + E) log V) time, where V is the number of vertices and E is the number of edges. By substituting a Fibonacci heap, the time complexity drops to O(E + V log V), which is significantly faster for dense graphs. Similarly, Prim's algorithm for finding a minimum spanning tree uses a priority queue to select the cheapest edge connecting the growing tree to an unvisited node Shortest connection networks and some generalizations[20]. In 1975, Donald B. Johnson demonstrated exactly how priority queues with update operations optimize these minimum spanning tree algorithms Priority queues with update and finding minimum spanning trees[21]. Furthermore, the A* search algorithm, foundational to artificial intelligence and pathfinding, uses a priority queue to evaluate nodes based on a heuristic cost function defined as f(n) = g(n) + h(n), where g(n) is the exact cost from the start node and h(n) is the estimated cost to the goal A Formal Basis for the Heuristic Determination of Minimum Cost Paths[22]. Network routing protocols, such as Open Shortest Path First, rely on these algorithms and priority queues to compute the shortest path tree for routing packets across the internet.

Operating Systems and Event Loops: Systems that must execute tasks at specific future times rely on heaps to manage timers. The libuv library, which provides the asynchronous I/O event loop for Node.js, uses a min-heap to manage scheduled callbacks. The timer with the closest expiration time is kept at the root of the heap, allowing the event loop to determine exactly how many milliseconds to block the thread while waiting for network I/O Design overview - libuv documentation[23]. In multi-core environments, operating systems often require concurrent access to priority queues. Researchers have developed specialized locking mechanisms and lock-free algorithms to allow multiple threads to insert and extract elements from a shared heap simultaneously without corrupting the tree structure An efficient algorithm for concurrent priority queue heaps[24].

Data Compression: Huffman coding, a foundational data compression algorithm invented in 1952, builds a minimum-redundancy prefix tree based on character frequencies A Method for the Construction of Minimum-Redundancy Codes[25]. The standard algorithm for constructing a Huffman tree uses a min-heap to repeatedly extract the two nodes with the lowest frequencies, merge them into a single parent node, and insert the parent back into the heap until only the root remains.

Sorting: While Quicksort is generally faster than Heapsort due to better cache locality, Heapsort is immune to worst-case data inputs. To combine the benefits of both, many standard libraries implement Introsort, introduced by David Musser in 1997 Introspective Sorting and Selection Algorithms[26]. Introsort begins by using Quicksort, but if it detects that the recursion depth has exceeded a specific threshold, it switches to Heapsort to guarantee an O(n log n) completion time.

Significance and Tradeoffs

The binary heap represents a highly optimized compromise in data structure design. By flattening a tree into an array, it uses exactly the amount of memory required to store the elements, with zero overhead for pointers. Its strict O(log n) bounds for insertion and extraction make it highly predictable, which is a requirement for real-time systems and operating system schedulers.

Despite these advantages, the binary heap has specific limitations. It is not a general-purpose search tree. Finding an arbitrary element in a binary heap requires an O(n) linear scan, because the heap property provides no horizontal ordering between sibling nodes. If an application requires frequent searches for arbitrary elements, a binary search tree is required instead.[28]

Furthermore, the decrease_key operation in a standard binary heap requires knowing the exact array index of the element being modified. Because the heap does not support fast searching, maintaining the ability to decrease a key requires an external hash table to map element identifiers to their current array indices. Whenever an element is moved during a sift-up or sift-down operation, the hash table must be updated. This adds a constant factor of time overhead and requires O(n) additional memory, negating the primary space advantage of the implicit array representation.

Cache locality presents another tradeoff on modern hardware. While the top levels of the heap fit neatly into CPU L1 and L2 caches, traversing down a large heap causes the array indices to jump by factors of two. For very large datasets, the distance between a node and its children grows exponentially, defeating hardware prefetchers and resulting in frequent cache misses. In scenarios involving massive datasets that exceed main memory, standard binary heaps perform poorly, and specialized cache-oblivious priority queues or B-trees must be employed On RAM priority queues[27].

Concurrency presents a final challenge. In a multi-core system, a single binary heap becomes a concurrency bottleneck because the root node must be locked during every extraction, serializing access. While concurrent priority queues exist, they often abandon the strict array-based binary heap in favor of skip lists or relaxed balancing rules.

Even with these limitations, the standard binary heap remains the default choice for implementing priority queues. Its conceptual simplicity, lack of pointer overhead, and strict worst-case guarantees ensure its continued relevance in algorithm design and systems engineering.

Sources (34)
  1. mitpress.mit.edu: Introduction to Algorithms, Fourth Edition
  2. www-cs-faculty.stanford.edu: The Art of Computer Programming, Volume 3
  3. Algorithm 232: Heapsort
  4. Algorithm 245: Treesort 3
  5. Average-case results on heapsort
  6. BOTTOM-UP-HEAPSORT, a new variant of HEAPSORT beating, on an average, QUICKSORT
  7. Min-max heaps and generalized priority queues
  8. A data structure for manipulating priority queues
  9. Fibonacci heaps and their uses in improved network optimization algorithms
  10. The pairing heap: a new form of self-adjusting heap
  11. Self-adjusting heaps
  12. ACM Digital Library
  13. heapq - Heap queue algorithm
  14. Class PriorityQueue
  15. std::priority_queue
  16. Struct std::collections::BinaryHeap
  17. Package heap
  18. PriorityQueue Class
  19. A note on two problems in connexion with graphs
  20. Shortest connection networks and some generalizations
  21. Priority queues with update and finding minimum spanning trees
  22. A Formal Basis for the Heuristic Determination of Minimum Cost Paths
  23. Design overview - libuv documentation
  24. An efficient algorithm for concurrent priority queue heaps
  25. A Method for the Construction of Minimum-Redundancy Codes
  26. Introspective Sorting and Selection Algorithms
  27. On RAM priority queues
  28. Derick Wood, Data Structures, Algorithms, and Performance (Addison-Wesley Longman, 1993)
  29. Jan L. Harrington, Object-oriented C++ Data Structures for Real Programmers (Morgan Kaufmann, 2002)
  30. Kurt Mehlhorn, Peter Sanders, Algorithms and Data Structures (Springer Science & Business Media, 2008)
  31. Steven S. Skiena, The Algorithm Design Manual (Springer Nature, 2020)
  32. Lev D. Beklemishev, Daniil V. Musatov, Computer Science -- Theory and Applications (Springer, 2015)
  33. Jiangjun Tang, George Leu, Hussein A. Abbass, Simulation and Computational Red Teaming for Problem Solving (John Wiley & Sons, 2019)
  34. Peter Sanders, Kurt Mehlhorn, Martin Dietzfelbinger, Sequential and Parallel Algorithms and Data Structures (Springer Nature, 2019)

Recommended reading