DEV Community

Alex
Alex

Posted on

I built a 500x faster ULID generator for React Native (JSI + C++)

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:

  1. Lexicographic sort order by creation time (great for DB indexes)
  2. 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: getrandom when available, otherwise /dev/urandom
  • last resort: std::random_device on 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
Enter fullscreen mode Exit fullscreen mode

With monotonic ULID (this library):

id1: 01HGW4Z6C8ABCDEFGHIJKLMNOP
id2: 01HGW4Z6C8ABCDEFGHIJKLMNPQ  // guaranteed > id1
id3: 01HGW4Z6C8ABCDEFGHIJKLMNPR  // guaranteed > id2
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Generate:

import { ulid } from "react-native-ulid-jsi";

const id = ulid();
Enter fullscreen mode Exit fullscreen mode

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)