Adam Johnson introduces emojet, a new Python emoji lookup library written as a Rust extension using PyO3 and maturin. It mirrors the API of the widely-used emoji package but is 3.5x faster for emojize(), about 70x faster for demojize(), imports in under a millisecond versus ~20ms, and uses roughly 40% less memory. The speedup comes from baking Unicode emoji data into static tables (a perfect hash function and a trie) rather than parsing a 520KB JSON file at import time. The project originated from profiling slow-to-import packages while optimizing startup time for a client (Rippling), and was built with help from Claude.

5m read timeFrom adamj.eu
Post cover image
Table of contents
The motivationBenchmarkationHow it worksFin

Questions this post answers

Is there a faster alternative to the Python emoji package for converting emoji to names and back?

Yes, emojet is a Rust-based Python library that replicates the core API of the emoji package (emojize and demojize) but runs about 3.5x faster for emojize(), roughly 70x faster for demojize(), and imports in under a millisecond compared to about 20ms for emoji 2.15.0. It also uses roughly 40% less memory, going from 29.2 MB down to 17.4 MB when importing and calling demojize() once. Developers chasing faster Python startup times can track new performance-focused packages like this on daily.dev.

Why does the Python emoji package take so long to import?

Most of its import time comes from loading and parsing a 520 KB JSON file containing emoji characters and names, which took about 22ms in profiling. This overhead is avoidable because the underlying emoji data rarely changes, only with Unicode's annual updates, making it a good candidate for baking into a compiled binary format instead of parsing JSON at runtime. Anyone auditing import-time bottlenecks in Python apps can follow packaging techniques like this via daily.dev.

How does emojet achieve fast emoji lookups internally?

It stores all emoji data as static tables compiled directly into a Rust extension module: a perfect hash function over 5,316 English names and aliases gives collision-free, single-probe lookups for emojize(), while a static trie over the code points of 5,225 emoji sequences drives the scanner behind demojize(). Nothing is parsed at import time, so the OS pages in the read-only data section on demand. Developers designing low-latency lookup structures can dig into implementation patterns like this through daily.dev.

318 Impressions