IndexedHeap
import { IndexedHeap } from "jsr:@ende/data-structures/unstable-indexed-heap";class · experimental
class IndexedHeap<K, P = number> implements Iterable<HeapEntry<K, P>>A priority queue that supports looking up, removing, and re-prioritizing
entries by key. Each entry is a unique (key, priority) pair. The entry
with the smallest priority (under the comparator) is always at the front.
Unlike BinaryHeap, which only allows popping the top element,
IndexedHeap lets you delete or change the priority of any entry by its
key in logarithmic time.
Priorities default to number and are sorted smallest-first via
ascend. Pass a different comparator (e.g.,
descend for max-heap order) or a different priority type
(e.g., [number, number] for stable tie-breaking) via the compare
option.
| Method | Time complexity |
|---|---|
| peek() | Constant |
| peekKey() | Constant |
| peekPriority() | Constant |
| pop() | Logarithmic in the number of entries |
| push(key, priority) | Logarithmic in the number of entries |
| set(key, priority) | Logarithmic in the number of entries |
| delete(key) | Logarithmic in the number of entries |
| has(key) | Constant |
| getPriority(key) | Constant |
| toArray() | Linear in the number of entries |
| clear() | Linear in the number of entries |
| drain() | Linearithmic in the number of entries |
| [Symbol.iterator] | Linear in the number of entries |
Iterating with for...of or the spread operator yields entries in
arbitrary (heap-internal) order without modifying the heap. To
consume entries in sorted order, use
drain.
Priorities are stored by reference. For primitive priority types
(number, bigint, string, etc.) this is invisible. With object
priorities such as tuples, treat them as immutable: the entries
returned by peek,
peekPriority,
getPriority,
toArray, and iteration
share the priority reference with the heap, so mutating a returned
priority's contents (e.g., heap.peek().priority[0] = 0) corrupts
the heap invariant. Use
set to change a key's
priority. set itself uses reference equality (===) to short-circuit
when the priority is unchanged, so passing a structurally-equal but
distinct object/tuple priority replaces the reference and runs a sift
(no observable reorder, but not a no-op).
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("a", 3);
heap.push("b", 1);
heap.push("c", 2);
assertEquals(heap.peek(), { key: "b", priority: 1 });
assertEquals(heap.pop(), { key: "b", priority: 1 });
assertEquals(heap.size, 2);
assertEquals([...heap.drain()], [
{ key: "c", priority: 2 },
{ key: "a", priority: 3 },
]);
Example: Max-heap via descend
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { descend } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>(null, { compare: descend });
heap.push("a", 1);
heap.push("b", 5);
heap.push("c", 3);
assertEquals(heap.peek(), { key: "b", priority: 5 });
Example: Tuple priority for stable tie-breaking
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
// Order primarily by score, then by insertion order for ties.
const heap = new IndexedHeap<string, [number, number]>(null, {
compare: (a, b) => a[0] - b[0] || a[1] - b[1],
});
heap.push("first", [5, 0]);
heap.push("second", [5, 1]);
heap.push("third", [3, 2]);
assertEquals([...heap.drain()].map((e) => e.key), [
"third",
"first",
"second",
]);
Constructors
constructor
new IndexedHeap(entries?: Iterable<readonly [K, P]> | null, ...options: IndexedHeapCompareArgs<P>)experimental
Creates an empty IndexedHeap, optionally populated from an
iterable of [key, priority] pairs and/or configured with a custom
comparator. Heapified in linear time.
Use IndexedHeap.from for inputs that are array-like instead
of iterable, or to copy from another IndexedHeap.
entries?: Iterable<readonly [K, P]> | nullAn optional iterable of
[key, priority]pairs. Each key must be unique; duplicates throw aTypeError. Passnullorundefinedto create an empty heap with options....options: IndexedHeapCompareArgs<P>Optional configuration when
Pisnumber,bigint, orstring; required whenPis any other type (e.g. tuples or objects) so the heap has a valid comparator.compareoverrides the default ascending-numeric ordering; passdescendfor max-heap order, or a custom function for tuple/bigint/etc. priorities.
Example: Empty heap
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
assertEquals(heap.size, 0);
Example: With initial entries
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>([["a", 3], ["b", 1], ["c", 2]]);
assertEquals(heap.peek(), { key: "b", priority: 1 });
Example: From a Map
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const tasks = new Map([["task-a", 3], ["task-b", 1]]);
const heap = new IndexedHeap(tasks);
assertEquals(heap.peek(), { key: "task-b", priority: 1 });
Example: Max-heap via descend
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { descend } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>([["a", 1], ["b", 5], ["c", 3]], {
compare: descend,
});
assertEquals(heap.peek(), { key: "b", priority: 5 });
new IndexedHeap(entries?: Iterable<readonly [K, P]> | null, options?: { compare?: (a: P, b: P) => number })Static methods
from
static from<K, P = number>(collection: IndexedHeap<K, P>, options?: { compare?: (a: P, b: P) => number }): IndexedHeap<K, P>experimental
Create a shallow copy of an existing IndexedHeap. The
source's comparator is inherited unless options.compare overrides
it; when overridden, the copy is re-heapified in O(n).
Because the source already carries a valid comparator, compare
stays optional regardless of the priority type P.
collection: IndexedHeap<K, P>The source
IndexedHeapto copy.options?: { compare?: (a: P, b: P) => number }Optional configuration. Omitting it inherits the source's comparator; passing
compareoverrides it and triggers a re-heapify.
Returns
A new heap containing all entries from the source heap.
Example: Creating from another IndexedHeap (shallow copy)
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const original = new IndexedHeap<string>();
original.push("x", 10);
original.push("y", 5);
const copy = IndexedHeap.from(original);
assertEquals(copy.peek(), { key: "y", priority: 5 });
assertEquals(copy.size, 2);
assertEquals(original.size, 2);
Example: Re-ordering an existing heap with a different comparator
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { descend } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const minHeap = new IndexedHeap<string>([["a", 1], ["b", 5], ["c", 3]]);
const maxHeap = IndexedHeap.from(minHeap, { compare: descend });
assertEquals(minHeap.peek(), { key: "a", priority: 1 });
assertEquals(maxHeap.peek(), { key: "b", priority: 5 });
static from<K, P = number>(collection: Iterable<readonly [K, P]> | ArrayLike<readonly [K, P]>, ...options: IndexedHeapCompareArgs<P>): IndexedHeap<K, P>experimental
Create a new IndexedHeap from an iterable or array-like
of [key, priority] pairs.
When the priority type P is number, bigint, or string, the
default ascend comparator orders correctly and the
options bag is optional. For any other P (tuples, objects), the
compare option is required at the type level so the heap has a
valid comparator — otherwise ascend would silently produce
incorrect ordering.
Use the IndexedHeap.from overload that takes an existing
IndexedHeap to copy from another heap instead.
collection: Iterable<readonly [K, P]> | ArrayLike<readonly [K, P]>An iterable or array-like of
[key, priority]pairs....options: IndexedHeapCompareArgs<P>Optional configuration when
Pisnumber,bigint, orstring; required whenPis any other type.compareoverrides the default ascending-numeric ordering.
Returns
A new heap containing all entries from the collection.
Example: Creating from an array of pairs
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = IndexedHeap.from([["a", 3], ["b", 1], ["c", 2]]);
assertEquals(heap.peek(), { key: "b", priority: 1 });
assertEquals(heap.size, 3);
Example: Creating from a Map (iterable of [key, value] pairs)
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const map = new Map([["task-a", 3], ["task-b", 1]]);
const heap = IndexedHeap.from(map);
assertEquals(heap.peek(), { key: "task-b", priority: 1 });
Example: Tuple priority requires an explicit comparator
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = IndexedHeap.from<string, [number, number]>(
[["a", [5, 0]], ["b", [3, 1]]],
{ compare: (a, b) => a[0] - b[0] || a[1] - b[1] },
);
assertEquals(heap.peek(), { key: "b", priority: [3, 1] });
static from<K, P = number>(collection: IndexedHeap<K, P> | Iterable<readonly [K, P]> | ArrayLike<readonly [K, P]>, options?: { compare?: (a: P, b: P) => number }): IndexedHeap<K, P>Properties
[Symbol.toStringTag]
readonly [Symbol.toStringTag]: "IndexedHeap"experimental
A string tag for the class, used by Object.prototype.toString().
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
assertEquals(heap[Symbol.toStringTag], "IndexedHeap");
Methods
push
push(key: K, priority: P): voidexperimental
Insert a new key with the given priority. Throws if the key already
exists — use set for upsert
semantics instead.
key: KThe key to insert.
priority: PThe priority (smaller = higher priority by default).
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("task-1", 10);
assertEquals(heap.size, 1);
assertEquals(heap.peek(), { key: "task-1", priority: 10 });
pop
pop(): HeapEntry<K, P> | undefinedexperimental
Remove and return the front entry (smallest priority), or undefined
if the heap is empty. The returned entry is removed from the heap so
the caller owns it.
Returns
The front entry, or undefined if empty.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("a", 2);
heap.push("b", 1);
assertEquals(heap.pop(), { key: "b", priority: 1 });
assertEquals(heap.pop(), { key: "a", priority: 2 });
assertEquals(heap.pop(), undefined);
peek
peek(): HeapEntry<K, P> | undefinedexperimental
Return the front entry (smallest priority) without removing it, or
undefined if the heap is empty.
The returned entry is a fresh wrapper, but for object-typed priorities
the priority field shares its reference with the heap — see the
class-level note on treating object priorities as immutable.
Returns
A wrapper around the front entry, or undefined if empty.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("x", 5);
heap.push("y", 3);
assertEquals(heap.peek(), { key: "y", priority: 3 });
assertEquals(heap.size, 2);
peekKey
peekKey(): K | undefinedexperimental
Return the key of the front entry (smallest priority), or undefined
if the heap is empty. Unlike
peek, does not allocate a
wrapper object.
Returns
The key of the front entry, or undefined if empty.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("x", 5);
heap.push("y", 3);
assertEquals(heap.peekKey(), "y");
assertEquals(heap.size, 2);
peekPriority
peekPriority(): P | undefinedexperimental
Return the priority of the front entry (smallest priority), or
undefined if the heap is empty. Unlike
peek, does not allocate a
wrapper object.
Returns
The priority of the front entry, or undefined if empty.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("x", 5);
heap.push("y", 3);
assertEquals(heap.peekPriority(), 3);
assertEquals(heap.size, 2);
delete
delete(key: K): booleanexperimental
Remove the entry with the given key.
key: KThe key to remove.
Returns
true if the key was present, false otherwise.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("a", 1);
heap.push("b", 2);
assertEquals(heap.delete("a"), true);
assertEquals(heap.delete("z"), false);
assertEquals(heap.size, 1);
set
set(key: K, priority: P): voidexperimental
Set the priority of a key, inserting it if absent. This is the upsert
counterpart to push (which
throws on existing keys) and is the natural operation for relaxation
steps in graph algorithms like Dijkstra's.
When key already exists, the no-op fast path uses reference equality
(===) to skip work when the new priority is identical to the old.
For object/tuple priorities this means a structurally-equal but
distinct reference still replaces the stored reference and runs a
sift (no observable reorder, but not free). See the class-level note
on treating object priorities as immutable.
key: KThe key to insert or update.
priority: PThe priority to set.
Example: Inserting and updating
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.set("a", 10);
assertEquals(heap.getPriority("a"), 10);
heap.set("a", 5);
assertEquals(heap.getPriority("a"), 5);
Example: Decrease-key reorders the heap
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("a", 10);
heap.push("b", 20);
heap.set("b", 1);
assertEquals(heap.peek(), { key: "b", priority: 1 });
has
has(key: K): booleanexperimental
Check whether the key is in the heap.
key: KThe key to look up.
Returns
true if the key is present, false otherwise.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("a", 1);
assertEquals(heap.has("a"), true);
assertEquals(heap.has("b"), false);
getPriority
getPriority(key: K): P | undefinedexperimental
Return the priority of the given key, or undefined if not present.
key: KThe key to look up.
Returns
The priority of the key, or undefined if not present.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("a", 42);
assertEquals(heap.getPriority("a"), 42);
assertEquals(heap.getPriority("b"), undefined);
size
get size(): numberexperimental
The number of entries in the heap.
Returns
The number of entries in the heap.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
assertEquals(heap.size, 0);
heap.push("a", 1);
assertEquals(heap.size, 1);
clear
clear(): voidexperimental
Remove all entries from the heap.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("a", 1);
heap.push("b", 2);
heap.clear();
assertEquals(heap.size, 0);
assertEquals(heap.isEmpty(), true);
isEmpty
isEmpty(): booleanexperimental
Check whether the heap is empty.
Returns
true if the heap is empty, false otherwise.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
assertEquals(heap.isEmpty(), true);
heap.push("a", 1);
assertEquals(heap.isEmpty(), false);
drain
drain(): IterableIterator<HeapEntry<K, P>>experimental
Create an iterator that removes and yields every entry from
smallest to largest priority. The heap is empty when the iterator
finishes. If iteration is abandoned early (e.g. via break),
the heap retains the remaining entries.
Returns
An iterator yielding entries from smallest to largest priority.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("a", 3);
heap.push("b", 1);
heap.push("c", 2);
assertEquals([...heap.drain()], [
{ key: "b", priority: 1 },
{ key: "c", priority: 2 },
{ key: "a", priority: 3 },
]);
assertEquals(heap.size, 0);
toArray
toArray(): HeapEntry<K, P>[]experimental
Return a shallow copy of all entries as an array. The order is the internal heap-array order (not sorted by priority). The heap is not modified.
Use drain to retrieve entries
in sorted (smallest-first) order.
Each returned entry is a fresh wrapper, but for object-typed priorities
the priority field shares its reference with the heap — see the
class-level note on treating object priorities as immutable.
Returns
An array of entries in arbitrary (heap-internal) order.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("a", 3);
heap.push("b", 1);
heap.push("c", 2);
const arr = heap.toArray();
assertEquals(arr.length, 3);
assertEquals(heap.size, 3);
[Symbol.iterator]
[Symbol.iterator](): IterableIterator<HeapEntry<K, P>>experimental
Yield all entries without removing them. The order is the internal
heap-array order (not sorted by priority). The heap is not modified
(unlike BinaryHeap, whose iterator drains).
Use drain to iterate in
sorted (smallest-first) order (which empties the heap).
Mutating the heap (push, pop, set, delete, clear) while
iterating is not supported and may skip or repeat entries.
Each yielded entry is a fresh wrapper, but for object-typed priorities
the priority field shares its reference with the heap — see the
class-level note on treating object priorities as immutable.
Returns
An iterator yielding entries in arbitrary (heap-internal) order.
Example: Usage
import { IndexedHeap } from "@ende/data-structures/unstable-indexed-heap";
import { assertEquals } from "@ende/assert";
const heap = new IndexedHeap<string>();
heap.push("a", 3);
heap.push("b", 1);
heap.push("c", 2);
const keys = [...heap].map((e) => e.key);
assertEquals(keys.length, 3);
assertEquals(heap.size, 3);