npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

frozendict

v2.1.1

Published

🧊 Blazingly fast, native immutable hashmap for Node.js, powered by Rust.

Readme

🧊 FrozenDict

frozendict logo

Crates.io Docs.rs PyPI npm License: MIT CI

frozendict is a state of the art world's most memory-efficient immutable hashmap written in 100% safe Rust, with native Python and Node.js bindings 🗿.

frozendict banner

🚀 Installation

| Platform | Command | | ----------------- | --------------------------------------------------------------------------------------- | | Rust library | cargo add frozendict | | Python | pip install frozndict | | Node.js | npm i frozendict | | Debian/Ubuntu | Download .deb from GitHub Releases | | RHEL/Fedora | Download .rpm from GitHub Releases |

🤔 What does this crate provide?

frozendict provides a fully immutable, hashable dictionary for Python and Node.js backed by a high-performance Rust core. It:

  • Stores entries in a single contiguous sorted heap allocation, zero per-entry heap overhead.
  • Looks up keys in O(log n) via binary search, no hashing, no pointer-chasing, excellent cache behaviour.
  • Caches its hash at construction, repeated hash() calls are O(1).
  • Integrates with Python's dict protocol (keys(), values(), items(), pickle, copy, | merge operator).
  • Exposes a native Node.js FrozenDict class with TypeScript declarations via napi-rs.
  • Provides functional update primitives: merge, with, without, intersection, union, difference.

💡 Why sorted slice + binary search?

| Property | FrozenMap (this crate) | HashMap / BTreeMap | | ---------------- | ---------------------------------- | ----------------------------- | | Memory overhead | Zero: exactly n × entry_size | 1.5-2× allocator overhead | | Allocation count | One at construction | One per entry (BTreeMap) | | Lookup | O(log n) binary search | O(1) / O(log n) | | Cache behaviour | Excellent: sequential prefetch | Pointer-chasing on tree nodes | | Mutation | Impossible by design | &mut self methods exist | | Hash stability | O(1): pre-computed at init | O(n) on every hash() call |

Binary search beats hash-map lookup for maps with fewer than ~64 entries because it avoids hashing and pointer-chasing. For larger maps the memory savings (up to 2×) and cache locality more than compensate. See Cache-oblivious binary search and the AHash paper for background.

🦀 Rust

[dependencies]
frozendict = "2.1.1"
use frozendict::frozen_map::FrozenMap;

let map: FrozenMap<&str, i32> = FrozenMap::new([("b", 2), ("a", 1)]);
assert_eq!(map["a"], 1);
assert_eq!(map.len(), 2);

let updated = map.with("c", 3);
assert_eq!(updated.len(), 3);

let merged = updated.merge(&FrozenMap::new([("a", 99)]));
assert_eq!(merged["a"], 99);

For the full API reference see RUST.md.

🐍 Python

pip install frozndict
from frozndict import FrozenDict

d = FrozenDict({"a": 1, "b": 2})
print(d["a"])          # 1
print(hash(d))         # stable integer (pre-computed at construction)

d2 = d | {"c": 3}      # returns a new FrozenDict
print(d2.keys())

For full docs see PYTHON.md.

🟩 Node.js

npm install frozendict
const { frozenDict, FrozenDict } = require("frozendict");

const d = frozenDict({ a: 1, b: "hello", c: [1, 2, 3] });
console.log(d.get("a")); // 1
console.log(d.size); // 3
console.log(d.has("z")); // false

const d2 = d.merge({ d: true });
console.log(d2.size); // 4
console.log(d2.toJSON()); // '{"a":1,"b":"hello","c":[1,2,3],"d":true}'

For full docs see NODE.md.

🔭 Features

| Feature | Default | Description | | -------- | ------- | ---------------------------------------- | | python | ❌ | Python extension module via PyO3/maturin | | node | ❌ | Node.js native add-on via napi-rs |

📊 Benchmarks

🚀 Performance Highlights

frozndict operates natively at the mathematical speed limits of the hardware, heavily outperforming its C counterparts and standard dictionary data structures on several fronts.

The following is a 1000-element dictionary micro-benchmark comparison in seconds per operation (smaller is better), measured on x86-64 Linux with LTO=fat, opt-level=3, codegen-units=1, panic=abort, target-cpu=native.

| Operation (1000 items) | Python dict | frozendict (C) | immutables.Map | frozndict 🧊 | | ---------------------- | ----------- | -------------- | -------------- | ------------ | | Construction | 6.41 µs 🏆 | 7.73 µs | 244.91 µs | 98.72 µs | | Clone O(1) | 6.37 µs | 69.13 ns 🏆 | 411.89 ns | 117.24 ns | | Equality | 19.18 µs | 19.61 µs | 24.39 ns 🏆 | 34.92 ns | | Iteration | 7.10 µs | 7.19 µs | 14.89 µs | 4.13 µs 🏆 | | copy() | 6.45 µs | 323.22 ns | 310.04 µs | 63.25 ns 🏆 | | hash() | N/A | 168.11 ns | 45.38 ns | 45.25 ns 🏆 | | Lookup | 33.33 ns 🏆 | 56.03 ns | 47.48 ns | 82.98 ns |

Benchmarked with timeit (min of 7 runs × 2 000 iterations). Python 3.12.

🏆 Fastest Iteration in Class

Keys are stored in a single contiguous sorted Box<[K]>, sequential iteration has perfect prefetch behaviour. At 1000 elements, frozndict iterates 1.9× faster than frozendict (C) and 3.5× faster than immutables.Map.

🏆 Smallest copy() Overhead

copy() and __deepcopy__() share the backing Arc<FrozenDictInner>, O(1), just an atomic reference count increment. At 64 ns, frozndict is 5× faster than frozendict (C) on copy.

Deterministic O(1) Pre-Computed Hashing

The dict-level hash is XOR-combined over all (k, v) pairs exactly once at construction via inline multiplicative mixing (0x9e3779b97f4a7c15, 0x517cc1b727220a95). Subsequent hash() calls are a single field read, O(1), allocating nothing.

O(1) Clone & Identity Equality

A shared Arc<FrozenDictInner> is re-used across the original, all copies, and frozendict(existing_fd) constructors. No data is ever duplicated. __eq__ short-circuits in O(1) via Arc::ptr_eq before comparing pre-computed hashes.

Smallest Memory Footprint

Entries sit in a SoA layout: a Box<[K]> for keys and a Box<[V]> for values, two contiguous allocations. Binary search only touches the key array: 2× smaller cache footprint compared to AoS layouts on large value types.

Infallible Rust-Level Immutability

Mutation is blocked at the Rust binary level. There are no &mut self methods, not just descriptor tricks.

Rust micro-benchmarks (cargo bench)

| Benchmark | Time | | ------------------------------ | -------- | | construction/4 | ~105 ns | | construction/64 | ~821 ns | | construction/1024 | ~16.1 µs | | construction/65536 | ~1.32 ms | | lookup/hit/4 | ~5.94 ns | | lookup/hit/64 | ~5.83 ns | | lookup/hit/1024 | ~5.87 ns | | lookup/hit/65536 | ~5.83 ns | | lookup/miss/64 | ~6.48 ns | | lookup/miss/65536 | ~6.51 ns | | iteration/keys/65536 | ~20.8 µs | | iteration/values/65536 | ~20.8 µs | | iteration/items/65536 | ~56.2 µs | | hash/precomputed/65536 | ~13.1 ns | | equality/equal/16384 | ~4.82 µs | | equality/unequal/16384 | ~969 ps | | functional/merge/256 | ~7.31 µs | | functional/with/4096 | ~64.6 µs | | functional/without/4096 | ~79.7 µs | | fromkeys/4096 | ~56.9 µs | | string_keys/lookup/hit_1024 | ~12.9 ns | | string_keys/lookup/miss_1024 | ~5.58 ns |

Run benchmarks yourself:

cargo bench                              # Rust
pip install frozndict frozendict immutables
python benchmarks/bench_compare.py      # Python comparison

🔒 Safety

This crate uses #![forbid(unsafe_code)] in all Rust modules except the Node.js FFI layer, which requires unsafe for napi-rs interop. Every other byte of implementation is safe Rust.

📚 Further Reading

📄 License

Licensed under the MIT License.