Home › Data structures & hashing › Ordered structures
algorithm low

d-ary heap

Problem Large priority queues (timers, event schedulers, shortest-path search, k-way merges) get slow because of memory cache misses. This makes a heap faster once it outgrows the cache.

Array heap with d children per node (d = 4 typical): half the binary heap's depth, siblings in one cache line, faster priority queues.

push O(log_d n); pop O(d·log_d n); peek O(1); build O(n)
priority-queueheaptimerscache-friendly
Overview

A binary heap stores node i's children at 2i+1, 2i+2; a d-ary heap uses d·i+1 … d·i+d. With d = 4 the tree is half as deep: pushes do half the comparisons, and pops do the same number of comparisons over half as many levels. Each level is a likely cache miss, and four children of 8-16 bytes share one 64-byte line.

Key facts

Use when

  • Priority queues larger than L1/L2: timers, event schedulers, Dijkstra/A*, k-way merge.
  • Push-heavy workloads, e.g. Dijkstra with lazy deletion.

Avoid when

  • You need decrease-key or meld: use an indexed or pairing heap.
  • Millions of coarse timers: a hierarchical timing wheel is O(1).

Deep dive

How it works

In: a priority-keyed item to push, or a pop request · Out: the item with the smallest key · State: one flat array, children of i at d·i+1 … d·i+d · Cost: push O(log_d n), pop O(d·log_d n), peek O(1)

index:   0 | 1  2  3  4 | 5 6 7 8  9 10 11 12  13 14 15 16  17 18 19 20 | ...
          root  children   children of 1..4 (each group of 4 = one cache line for 16 B items, with the offset below)
parent(i) = (i - 1) / d      children(i) = d*i + 1 ..= d*i + d
  1. Push: append at the end, sift up: while smaller than parent, move parent down.
  2. Pop: take the root, move the last element to the root, sift down: find the smallest of up to d children, swap if smaller than it, repeat.
  3. Build: sift down every internal node from (n-2)/d down to 0: O(n).
  4. Hole technique: instead of swapping at every level, hold the moving element aside and shift elements into the hole; one write per level instead of three.

Code

/// Min-heap with D children per node.
pub struct DaryHeap<T: Ord, const D: usize> { v: Vec<T> }

impl<T: Ord, const D: usize> DaryHeap<T, D> {
    pub fn new() -> Self { DaryHeap { v: Vec::new() } }
    pub fn from_vec(v: Vec<T>) -> Self {
        let mut h = DaryHeap { v };
        if h.v.len() > 1 {
            for i in (0..=(h.v.len() - 2) / D).rev() { h.sift_down(i); } // O(n) heapify
        }
        h
    }
    pub fn len(&self) -> usize { self.v.len() }
    pub fn peek(&self) -> Option<&T> { self.v.first() }

    pub fn push(&mut self, x: T) {
        self.v.push(x);
        let mut i = self.v.len() - 1;
        while i > 0 {
            let p = (i - 1) / D;
            if self.v[i] >= self.v[p] { break; }
            self.v.swap(i, p);
            i = p;
        }
    }

    pub fn pop(&mut self) -> Option<T> {
        let last = self.v.pop()?;
        if self.v.is_empty() { return Some(last); }
        let top = std::mem::replace(&mut self.v[0], last);
        self.sift_down(0);
        Some(top)
    }

    fn sift_down(&mut self, mut i: usize) {
        let n = self.v.len();
        loop {
            let first = D * i + 1;
            if first >= n { return; }
            let end = (first + D).min(n);
            let mut m = first; // smallest child; the D children are contiguous, one or two cache lines
            for c in first + 1..end {
                if self.v[c] < self.v[m] { m = c; }
            }
            if self.v[m] >= self.v[i] { return; }
            self.v.swap(i, m);
            i = m;
        }
    }
}

// Usage: timers keyed by (deadline_ns, id).
// let mut h: DaryHeap<(u64, u32), 4> = DaryHeap::new(); h.push((now + 5_000_000, 7));

Complexity and constants

  • Height: log_d n; n = 10^7 → binary 24 levels, 4-ary 12, 8-ary 8.
  • Comparisons: push ≤ log_d n; pop ≤ d·log_d n (4-ary: 2·log2 n, 8-ary: 2.67·log2 n).
  • Heaps larger than cache: pop cost is roughly levels × miss latency (order of 80-100 ns each), so 4-ary pops are commonly 1.5-2× faster than binary at 10^7 elements; inside L1 the difference is small and binary can win.
  • Branchy child selection mispredicts on random keys; a branch-free min (cmov) over the 4 children helps.
  • Memory: exactly n·sizeof(T), no pointers.

Efficiency

  • Cost: pop walks log_d n levels, each a likely cache miss once the heap exceeds cache, order of 80-100 ns: 12 levels at 10^7 elements for d = 4 versus 24 for d = 2. Memory is exactly n·sizeof(T), no pointers. Bound: memory latency (compares are cheap).
  • Levers:
    • d = 4 with the 4 children in one cache line — about half the levels, commonly 1.5-2× faster pops than binary at 10^7 (order of; LaMarca-Ladner 1996) — heaps larger than L2
    • hole technique — one write per level instead of three swaps (Rust BinaryHeap) — large T
    • branch-free minimum over the d children — avoids mispredicts on random keys (order of) — random priorities
    • small (key, index) entries, payload elsewhere — more children per line — large payloads
    • lazy deletion instead of decrease-key — push duplicates, skip stale on pop — shortest-path search, timers
  • Naive → standard → state of the art:
    • sorted array or list · O(n) insert
    • binary heap · O(log2 n) levels, about 2 children per line
    • 4-ary aligned heap, or timing wheel for coarse timers · half the levels; O(1) per timer for the wheel

Implementation notes

  • Cache alignment (LaMarca & Ladner): allocate the array 64-byte aligned and offset it by D-1 slots; then each sibling group D·i+1 … D·i+D starts on a line boundary when D·sizeof(T) = 64 (4 × 16 B or 8 × 8 B).
  • Keep heap entries small: (key, index) pairs of 8-16 bytes; store payloads elsewhere.
  • Decrease-key: keep a pos[id] array updated on every move (indexed heap). Many shortest-path codes skip it and push duplicates with lazy deletion instead; that is often faster.
  • Stable order for equal priorities (FIFO timers): include a sequence number in the key.
  • Timer heaps (Go, libev) store the deadline inline in the heap entry so comparisons do not chase pointers.
  • Rust's BinaryHeap is a max-heap with d = 2 and uses the hole technique; use Reverse for min order.

Pitfalls

  • Off-by-one in parent/child formulas when switching from 1-based binary heap code: with 0-based arrays, parent = (i-1)/d.
  • Large T moved by value at every level; use the hole technique or indices.
  • Removing arbitrary elements (cancelled timers) without an index: O(n) search. Either index positions or mark cancelled and drop on pop.
  • Comparing floats with partial_cmp().unwrap(): NaN panics or corrupts order.

Variants and alternatives

Binary heapsmallest code, best for tiny heaps.
Pairing heapO(1) insert and meld, fast decrease-key in practice; pointer-based.
Radix heapmonotone integer priorities (Dijkstra with integer weights), O(1) amortized per op.
Hierarchical timing wheelO(1) timers when ordering within a tick does not matter.
B-heap / van Emde Boas layoutlay out subtrees in pages for out-of-core heaps.

In production

  • Language runtimes and event loops: .NET PriorityQueue (quaternary heap; source), Go runtime timers (timerHeapN = 4; source), libev (4-heap timers, EV_USE_4HEAP).
  • C++ libraries: Boost.Heap d_ary_heap.
  • Algorithms in graph routing, event simulation and k-way merging use the same heap as a priority queue.

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
dary_heapRusthanmertens/dary_heapcargo add dary_heap————BinaryHeap API with const-generic arity
Boost.HeapC++boostorg/heapbrew install boost————d_ary_heap, mutable with handles
.NET PriorityQueueC#dotnet/runtimen/a (in the .NET BCL)————quaternary min-heap
Rust BinaryHeapRustrust-lang/rustn/a (std)————binary (d = 2) heap with hole technique

Sources

  • Donald B. Johnson, Priority queues with update and finding minimum spanning trees, Information Processing Letters 4(3), 1975.
  • Anthony LaMarca, Richard E. Ladner, The Influence of Caches on the Performance of Heaps, ACM Journal of Experimental Algorithmics, 1996. paper
  • Boost.Heap documentation, d_ary_heap. docs

Used by

A* searchA planner must find the cheapest route between one start and one goal in a large graph of states, and exploring everything around the start is too slow.
arrival sequencing and scheduling · plannedRunway arrival sequencing with separation constraints (constrained position shifting, MIP/heuristics)
asset streaming priority · plannedPrioritized async loading of assets by distance and visibility under a memory budget with eviction
Assignment problem (Hungarian / Jonker–Volgenant)A system with n agents and n tasks, each pairing having a known cost, must give every agent exactly one task so that the total cost is lowest, and must do so again every frame, batch or scheduling round.Beam search decodingA system must output one whole sequence (a transcript, translation, text line or molecule) from a model that scores only the next item given the prefix. Choosing the locally best item each step can lock in a poor total.CDCL SAT solvingDeciding whether a huge set of yes/no constraints can all hold at once, for circuits, programs, packages or schedules, where blind search explodes and a "no" must be trusted.Contraction hierarchies (shortcut-based shortest paths)A service must answer millions of shortest-path questions on one large weighted graph that changes rarely, and a fresh search per question is too slow. It can afford minutes of one-time work on the graph.
cooperative ui scheduling · plannedPrioritized, interruptible render work split into time slices that yield to input
Count-min sketchEngineers must find the most frequent items (hot keys, top talkers, abusive clients) in a huge stream without storing a counter per distinct item. It also feeds cache admission decisions.
customizable route planning · plannedMulti-level overlay routing (CRP) with fast metric customization for live traffic
Dijkstra's algorithmSoftware must find the cheapest paths from one starting point to all other points in a network whose links have different non-negative costs.
discrete event simulation · plannedSimulation driven by a time-ordered event queue with deterministic, reproducible replay
diskann vamana · plannedVamana graph index on SSD with compressed vectors in RAM for billion-scale ANN
event driven logic simulation · plannedRTL and gate simulation with event wheels, delta cycles and 4-state logic
fair share scheduling · plannedDominant resource fairness and hierarchical fair-share queues
Fill-reducing orderingSolving a large sparse linear system exactly (meshes, circuits, constrained optimization) where factoring it in the order the unknowns happen to be numbered fills memory with new nonzeros and takes far too long.
flash garbage collection · plannedVictim block selection (greedy, cost-benefit), valid page migration and write amplification in SSDs
gillespie algorithm · plannedExact stochastic simulation of reaction networks; next-reaction and tau-leaping variants
graph coloring · plannedGreedy/DSatur colouring for conflict-free assignment such as radio channels
Graph partitioningDistributing a large graph (social network, mesh, database shards) across machines needs balanced parts with few links between them, because every cross-part edge costs network traffic.
hierarchical timestepping · plannedBlock power-of-two timesteps per particle for multi-scale dynamics
HNSWApplications that must find the vectors most similar to a query (embeddings search, RAG, recommendations) among millions of in-memory vectors, while still accepting new inserts, need fast approximate nearest-neighbour lookup.
instruction scheduling · plannedList scheduling over dependence DAGs with latency models; software pipelining of loops
Interest managementA server with many clients and many objects cannot send the whole world to everyone: bandwidth multiplies, and every hidden object sent leaks information to a modified client. The server must decide what each client needs to know.
k shortest paths · plannedYen's / Eppstein's k shortest loopless paths for alternative itineraries
label placement collision · plannedPlace map labels and icons without overlap using a collision grid and priorities
margin liquidation · plannedMark-to-market margin calls and forced liquidation of under-margined positions, incl. insurance fund and auto-deleveraging
maze routing · plannedLee and A*-based grid routing with rip-up and reroute for detailed routing
mesh simplification quadric · plannedEdge collapse with quadric error metrics to build mesh LODs
mimo detection · plannedLinear MMSE, SIC and sphere decoding to separate spatially multiplexed streams
min cost flow · plannedMinimum-cost flow (successive shortest paths, network simplex) for distribution and rebalancing
Minimum spanning treeSomeone designing a network, such as cabling, pipes or links, must connect every site at the lowest total cost. The same structure is also used for clustering and segmentation.
minimum weight perfect matching · plannedEdmonds blossom algorithm and sparse blossom for matching syndromes and graphs
Mixed-integer programming (branch and bound)Planning problems with yes/no decisions or counts under linear constraints (routing, rostering, unit commitment) need a provably optimal answer or a stated gap, not just a good-looking one.
multi agent path finding · plannedConflict-based search for collision-free paths of many robots on a grid
network update prioritization · plannedPriority accumulators per entity and client to fill a fixed per-tick bandwidth budget
pairing queue · plannedPair waiting players into fair matches by rating and latency, widening tolerances the longer each ticket waits
peer to peer swarming · plannedBitTorrent-style piece exchange: rarest-first, choking, tit-for-tat, hash-verified pieces
polygon clipping · plannedPolygon intersection/union/difference (Weiler-Atherton, Martinez) for spatial predicates
priority and preemption · plannedPriority classes, quota and evicting lower-priority work to admit higher
time dependent shortest path · plannedShortest paths where edge costs depend on departure time (FIFO property, timetable graphs)
Top-k selectionQueries like "top 10 by score", leaderboards and nearest-neighbour candidate lists need only the best few of many items, from arrays or streams, without paying to sort everything.
url frontier · plannedPrioritised per-host queues with politeness delays for crawling
voronoi diagrams · plannedFortune sweep and jump flooding to compute Voronoi regions and distance fields
watermarks · plannedEvent-time progress markers that bound lateness and trigger window results
wave function collapse · plannedConstraint-propagation tile synthesis: lowest-entropy collapse and backtracking
wear leveling · plannedSpread erases across flash blocks: dynamic and static wear leveling, read disturb refresh

Systems (18):

Alternatives and related