Beyond UUID: How Twitter's Snowflake IDs, ULID, CUID2, and Nano ID Work
Twitter, Discord, and Instagram all built custom ID systems because UUID couldn't handle time-sortability, distributed generation, and 64-bit constraints simultaneously. Here's how Snowflake IDs work, what ULID and CUID2 offer, and when each alternative makes sense.
By sadiqbd · June 9, 2026
Twitter, Discord, and Instagram all invented their own ID systems — and for good reasons UUID couldn't address
UUID v4 is the obvious answer to "how do I generate unique IDs?" — 122 bits of randomness, effectively impossible to collide, widely supported. Yet Twitter, Discord, and Instagram each chose to build custom ID generation systems rather than use UUIDs.
The reason is the same in each case: at very high scale, you need IDs that are sortable by creation time, can be generated without central coordination, encode metadata (like the generating machine), fit in 64 bits (rather than 128), and produce IDs that are monotonically increasing within any given time period.
Snowflake IDs: how Twitter solved the problem
Twitter's Snowflake ID format (open-sourced in 2010) packs 64 bits with:
[41 bits: millisecond timestamp] [10 bits: machine/worker ID] [12 bits: sequence number]
- 41 bits timestamp: milliseconds since a custom epoch (Twitter used November 4, 2010). 2^41 milliseconds = ~69 years of IDs. The custom epoch pushes the overflow ~69 years from the epoch date.
- 10 bits machine ID: up to 1,024 worker processes can generate IDs simultaneously without coordination.
- 12 bits sequence: up to 4,096 IDs per millisecond per worker.
Maximum throughput: 1,024 workers × 4,096 IDs/ms = ~4.2 billion IDs per second.
Key property: Snowflake IDs are time-sortable. Sort numerically and you sort by creation time (approximately — within the same millisecond, sequence number determines order). This eliminates the B-tree fragmentation problem of random UUIDs in database indexes.
IDs visible in the wild: Twitter tweet IDs, Discord server and user IDs, Instagram post IDs all embed timestamps:
# Extracting timestamp from a Twitter Snowflake ID
TWITTER_EPOCH = 1288834974657 # ms
def snowflake_to_timestamp(snowflake_id):
return (snowflake_id >> 22) + TWITTER_EPOCH
tweet_id = 1498128524688527363
# 1498128524688527363 >> 22 = 357166178 (offset from epoch)
# + 1288834974657 = 1645835126801 (ms since Unix epoch)
# = 2022-02-25 (date of that tweet)
ULID: Universally Unique Lexicographically Sortable Identifier
ULID was designed before UUID v7 was standardised as a practical alternative to UUID v4 for database primary keys.
Format: 26 characters in Crockford Base32 (a case-insensitive Base32 variant)
01ARZ3NDEKTSV4RRFFQ69G5FAV
|----------|----------------|
10 char TS 16 char random
(48 bits) (80 bits)
- 48-bit timestamp: millisecond precision, covers ~8,900 years
- 80 bits of randomness: fills the rest of the 128-bit total
Properties:
- Lexicographically sortable (string sort = time sort)
- URL-safe (no
+,/,=characters) - Case-insensitive
- Compatible with UUID storage (same 128-bit size)
- No dashes (26 chars vs UUID's 36)
UUID v7 vs ULID: UUID v7 (RFC 9562, 2024) achieves the same goals with broader standardisation and native database support. ULID predates v7 and remains common in codebases that adopted it before v7 existed. For new projects, UUID v7 has broader framework support; ULID's Crockford encoding has some ergonomic advantages.
CUID2: Collision-resistant Unique ID
CUID2 (the second generation of CUID) is designed for:
- Client-side generation (no server coordination needed)
- High-collision resistance even with many concurrent generators
- No timestamp embedding (no creation time inference)
- URL-safe output
Format: 24-character lowercase alphanumeric string, starting with a letter.
clh3btj870000qzrm1f3m5mmf
What makes it different:
- Uses a cryptographically secure hash (SHA-3) with multiple entropy sources (time, counter, fingerprint, random)
- Fingerprint mixes system information to differentiate generators
- First character is always a letter (valid JavaScript identifier, works as HTML IDs)
When CUID2 is the right choice:
- Client-side ID generation where you can't guarantee clock synchronisation
- Systems where timestamp inference is undesirable (privacy-sensitive IDs)
- IDs that will appear in URLs or HTML attributes (letter-prefixed, no special chars)
Nano ID: URL-safe compact random IDs
Nano ID generates random IDs using a configurable alphabet and length:
import { nanoid } from 'nanoid';
const id = nanoid(); // "V1StGXR8_Z5jdHi6B-myT" (21 chars, default)
Properties:
- Cryptographically secure (uses
crypto.getRandomValues) - Configurable length and alphabet
- Compact (21 chars default ≈ 126 bits of entropy — similar to UUID)
- URL-safe (no characters needing encoding)
Size advantage over UUID: UUID's 36-character string representation vs. Nano ID's 21 characters for equivalent entropy. In high-volume systems with IDs in URLs or responses, the 41% size reduction accumulates.
Nano ID is primarily random — no time component, not sortable. It's essentially a more compact, URL-safe alternative to UUID v4.
Comparison table
| System | Length | Sortable | Time-encoded | Random bits | Format |
|---|---|---|---|---|---|
| UUID v4 | 36 chars | No | No | 122 bits | Hyphenated hex |
| UUID v7 | 36 chars | Yes | Yes (ms) | 74 bits | Hyphenated hex |
| ULID | 26 chars | Yes | Yes (ms) | 80 bits | Crockford Base32 |
| Snowflake | ~19 digits | Yes | Yes (ms) | 12–22 bits | Integer |
| CUID2 | 24 chars | No | No | ~94 bits | Lowercase alphanum |
| Nano ID | 21 chars | No | No | ~126 bits | URL-safe alphanum |
Choosing the right ID format
For database primary keys (new project): UUID v7 — time-ordered, standard, 128-bit, widely supported in Postgres 17+, Java, Python.
For legacy systems already using ULID: stay with ULID; migration cost exceeds benefit.
For distributed systems needing high throughput with coordination: Snowflake-style IDs — allow each node to generate IDs independently with no central registry.
For client-generated IDs where timestamp exposure is undesirable: CUID2 or Nano ID.
For compact URL-friendly IDs: Nano ID (21 chars vs UUID's 36).
How to use the UUID Generator on sadiqbd.com
- Select the version — v4 for random, v7 for sortable database keys
- Generate — single or bulk
- Copy — paste into your application
For ULID, CUID2, and Nano ID: dedicated npm packages or language libraries are available. The UUID generator covers the UUID family.
Frequently Asked Questions
Can I use integers instead of UUIDs/Snowflakes for database IDs? Auto-incrementing integers are simpler and very efficient for single-server, single-database systems. The limitations appear at scale: no distributed generation without central coordination, IDs reveal the total count of records (a security concern for some applications), and merging databases with overlapping ID ranges is complex.
How does Discord's Snowflake differ from Twitter's? Different custom epoch and bit allocation. Discord uses January 1, 2015 as epoch. The bit layout is slightly different. The concept is the same — 64-bit time-ordered IDs with per-worker sequence numbers.
Is the UUID Generator free? Yes — completely free, no sign-up required.
UUID v4 is the right default for most applications. The alternatives — Snowflake, ULID, CUID2, Nano ID, UUID v7 — each solve a specific problem: time-ordering, distributed generation, privacy, or compactness. Knowing the options makes the right choice clear for each use case.
Try the UUID Generator free at sadiqbd.com — generate UUID v4 or v7 identifiers individually or in bulk.