If you build React Native apps long enough, you eventually hit a point where “small” things start showing up in performance profiles.
For me, one of those things was ID generation.
Chat messages, offline queues, analytics events, local DB rows, sync logs - you generate IDs everywhere. And once you start generating IDs a lot (sometimes thousands in one run), a pure JavaScript solution becomes surprisingly expensive.
So I built react-native-ulid-jsi: an ultra-fast ULID generator for React Native implemented in C++ via JSI, with monotonic ordering, thread-local state, and platform-native secure randomness.
Result: ~500x faster than a popular JS ULID implementation in my benchmark.
Why ULID (and not UUID)
ULID stands for Universally Unique Lexicographically Sortable Identifier.
A ULID is a 26-character string like:
01ARZ3NDEKTSV4RRFFQ69G5FAV
It’s composed of:
- 48-bit timestamp (milliseconds since epoch)
- 80-bit randomness
That gives you two huge benefits:
- Lexicographic sort order by creation time (great for DB indexes)
- Strong uniqueness without coordination
In practice, ULIDs are excellent for:
- database primary keys (B-tree friendly)
- event sourcing
- logging and telemetry
- distributed systems
- file names / resource IDs
UUIDs are fine for uniqueness, but they don’t preserve time ordering and usually fragment indexes harder.
The React Native problem: JavaScript ID generation is not “free”
Even if a JS ULID library is well-written, in React Native it still has typical JS costs:
- allocations and GC pressure
- repeated encoding work (Base32)
- randomness generation overhead
- runtime overhead in hot loops
- lots of “tiny” work that becomes big at scale
And in mobile apps, you often care about:
- battery
- smoothness on mid-tier devices
- cold start / background tasks
- performance under load
I wanted an ID generator that behaves like a native primitive:
fast, predictable, and cheap.
The solution: ULID in C++ via JSI (zero bridge overhead)
react-native-ulid-jsi is implemented in C++ and exposed to JavaScript via JSI.
That means:
- no React Native Bridge serialization
- no JSON conversion
- direct function call from JS into native
It supports both:
- New Architecture (Fabric + TurboModules)
- Old Architecture
Same API. Same performance benefits.
Benchmark: 1000 ULIDs in 0.17ms (iPhone 16 Pro)
I compared it against the official ulid/javascript package.
| Implementation | Time (1000 iterations) | Relative |
|---|---|---|
| react-native-ulid-jsi (JSI/C++) | 0.17ms | ~500x faster |
| JavaScript (ulid package) | 83.62ms | baseline |
Benchmark notes:
- device: iPhone 16 Pro
- build: production
- loop: 1000 ULID generations
Why it’s fast
The speed comes from several practical choices:
1) Zero bridge overhead
JSI binding means the call is direct, not marshaled through the RN Bridge.
2) Native secure randomness
The random portion uses platform-native secure RNG:
- iOS:
SecRandomCopyBytes - Android:
getrandomwhen available, otherwise/dev/urandom - last resort:
std::random_deviceon other platforms
3) Hand-optimized Base32 encoding
ULID uses Crockford’s Base32 (excluding ambiguous chars like I, L, O, U).
The encoding is implemented in C++ with tight control over memory and branching.
4) Monotonic generation with thread-local state
If you generate multiple ULIDs within the same millisecond, “pure random” ULIDs are not guaranteed to be sorted.
This library ensures monotonic ordering by keeping per-thread state:
- same ms - increment randomness
- time changed - generate fresh random bytes
- overflow - regenerate safely
5) Minimal allocations
Stack buffers. No heap churn. No dependency overhead.
Monotonic ULID: why it matters in real apps
If you generate IDs quickly (chat typing, bulk inserts, offline queue replay), you want ordering to reflect creation order.
Without monotonic behavior:
// Pure random: not guaranteed to be > lexicographically
id1: 01HGW4Z6C8ABCDEFGHIJKLMNOP
id2: 01HGW4Z6C8ZYXWVUTSRQPONMLK // could be "smaller" later
With monotonic ULID (this library):
id1: 01HGW4Z6C8ABCDEFGHIJKLMNOP
id2: 01HGW4Z6C8ABCDEFGHIJKLMNPQ // guaranteed > id1
id3: 01HGW4Z6C8ABCDEFGHIJKLMNPR // guaranteed > id2
Why you care:
• smoother DB inserts and index locality
• stable ordering when timestamps match
• better behavior in event streams and sync logs
Install:
yarn add react-native-ulid-jsi
cd ios && pod install
Generate:
import { ulid } from "react-native-ulid-jsi";
const id = ulid();
Technical details (for people who care)
• C++ implementation + JSI bindings
• secure randomness with platform fallbacks
• Crockford’s Base32 encoding
• supports iOS 12+ and Android API 21+
• works on New + Old RN architecture
• zero JavaScript dependency footprint
When you should use it
This library is especially useful when:
• you generate lots of IDs in a session
• you insert frequently into SQLite/Postgres indexes
• you want time-sortable IDs without storing separate timestamps
• you have offline-first queues / event logs
• you care about performance and battery
Links
• GitHub: https://github.com/pioner92/react-native-ulid-jsi
• npm: https://www.npmjs.com/package/react-native-ulid-jsi
Top comments (0)