"Generating an ID is easy."
Is it, though?
Every second, companies like Amazon, Stripe, Uber, Discord, and Netflix create millions of new records. Every order, payment, message, notification, and user account needs exactly one thing in common: a unique identifier.
It sounds almost trivial. Just keep an integer and increment it:
1, 2, 3, 4, 5...
Problem solved. Right?
Now imagine your application no longer runs on a single server. It looks more like this:
Load Balancer
│
┌───────────┴───────────┐
│ │
Server A Server B
│ │
└───────────┬───────────┘
│
Millions of Requests
Server A and Server B both receive a request at the exact same moment. Both independently decide the next ID should be 10542.
Congratulations. Two customers now own Order #10542. Your database is confused, your monitoring is screaming, and somewhere a backend engineer just canceled dinner plans.
Distributed systems have a remarkable talent for turning simple problems into fascinating engineering challenges. Counting is one of them.
Why IDs Matter More Than You'd Think
Almost every backend system depends on unique IDs. Consider a typical e-commerce platform: when a customer places an order, several services spring into action at once — Order, Payment, Inventory, Shipping, Notifications — each storing data independently.
Every record they create needs an identifier that is:
Unique — no exceptions, ever
Fast to generate — without becoming a bottleneck
Scalable — able to grow with the system
Reliable — available even when parts of the system aren't
What Makes an ID Generator "Good"
Let's define what "good" actually means for a production system. A solid ID generator should satisfy most of the following:
1. Global uniqueness. No two IDs should ever collide.
2. High throughput. A payment gateway or messaging platform might need millions of IDs per second.
3. Low latency. An API shouldn't wait hundreds of milliseconds for an ID. Ideally, this takes microseconds.
4. High availability. If one server crashes, the platform shouldn't stop creating records.
5. Scalability. A startup running on two servers today might run on two thousand next year.
The Obvious Solutions (and Why They Fall Apart)
Before reaching for something sophisticated, it's worth understanding the intuitive approaches. Because most of them work fine, right up until they don't.
Database AUTO_INCREMENT
The simplest option by far:
CREATE TABLE users (
id BIGINT AUTO_INCREMENT,
name VARCHAR(255)
);
Simple, reliable, easy to reason about. So what's the catch?
Picture every application server sending inserts to the same database:
App Server A ───┐
│
App Server B ───┼──► Database
│
App Server C ───┘
The database becomes the sole authority for every ID in the system. A textbook single point of failure. If it slows down, everything slows down. If it goes offline, nothing can be created. And as you add more servers, scaling only gets harder, not easier.
Random Numbers
Just generate a random integer?
8349284
1293874
7239182
Fast, and requires no coordination between servers. The catch is that... the more IDs you generate, the higher the odds of a collision.
UUID v4
Most developers have run into a UUID at some point:
550e8400-e29b-41d4-a716-446655440000
UUID v4 draws from such a large space of random bits that the odds of a collision are effectively negligible. It needs no central coordinator, no assigned machine IDs, and works virtually anywhere.
So why doesn't everyone just use UUIDs?
The Hidden Cost of Randomness
Most databases store rows physically ordered by primary key. Sequential IDs append neatly to the end:
1001
1002
1003
1004
1005
Databases love this — it's cheap and predictable. Now insert random UUIDs instead:
4fd2...
9ac1...
1ab7...
7de3...
Each insert can land anywhere inside the index. The database is constantly reshuffling its internal B-tree structure, which means more page splits, worse cache locality, and slower writes. On a laptop with a toy dataset, you'll never notice. At millions of inserts a day, it becomes a real, measurable cost.
Enter Twitter Snowflake
By 2010, Twitter was generating millions of tweets across hundreds of servers every day. Each tweet needed a unique ID, but relying on a central database to issue them had become a serious bottleneck.
So Twitter introduced Snowflake.
The idea was simple:
Let every machine generate its own IDs instead of asking a central service.
No database calls. No global locks. Minimal coordination.
Just a 64-bit number that each server can build on its own.
Snowflake encodes three things into every ID:
When it was created
Which machine created it
Which sequence number it was within that millisecond
With this, every server can independently generate unique, roughly time-ordered IDs at massive scale.
The Philosophy Behind Snowflake
Picture an e-commerce platform running hundreds of application servers. Every time an order comes in, a server needs a unique Order ID. One option is to ask a database for it hundreds of thousands of times per second.
Your database quickly becomes everyone's least favorite coworker: the one who gets interrupted every few seconds until they can't get anything else done.
Snowflake removes that dependency entirely. Every server generates IDs locally using only three pieces of information:
the current timestamp,
its own machine ID,
and a small counter for IDs created within the same millisecond.
No central coordinator. No lock contention. No network latency.
The Anatomy of a Snowflake ID
A Snowflake ID is a 64-bit signed integer. The highest bit is always 0, ensuring the value remains positive. The remaining 63 bits are divided into three fields:
┌──────┬──────────────┬───────────┬──────────┐
│ Sign │ Timestamp │ Worker ID │ Sequence │
├──────┼──────────────┼───────────┼──────────┤
│ 1 bit│ 41 bits │ 10 bits │ 12 bits │
└──────┴──────────────┴───────────┴──────────┘
Each field has a specific job.
Timestamp (41 bits)
The largest portion stores time, but not as a Unix timestamp. Instead, Snowflake records the number of milliseconds elapsed since a custom epoch chosen by your application.
current_time - custom_epoch
Using a custom epoch makes better use of the available bits. With 41 bits, you can represent roughly:
2^41 ≈ 2.2 trillion milliseconds ≈ 69 years
Starting from a recent date, such as 2025-01-01, gives your system decades of usable IDs without wasting range on years before your application even existed.
Worker ID (10 bits)
Each machine in the system is assigned a unique identifier.
Worker A → 1
Worker B → 2
Worker C → 3
With 10 bits, Snowflake supports up to 1,024 independent ID generators, allowing every server to produce IDs without coordinating with the others.
Sequence (12 bits)
Multiple IDs can be generated during the same millisecond by the same machine.
To keep them unique, Snowflake maintains a sequence counter:
0, 1, 2, 3 ... 4095
This allows a single worker to generate up to 4,096 IDs per millisecond, or roughly 4.1 million IDs per second.
Putting It All Together
Suppose a server generates an ID with:
timestamp = 100101...
worker = 17
sequence = 28
Each value is shifted into its assigned position and combined using bitwise OR:
id = (timestamp << 22) | (worker << 12) | sequence
The result is a single integer, something like:
739847238947239482
To your application, it's just another number.
Under the hood, it quietly encodes when it was created, which machine created it, and which request it was within that millisecond — all packed into 64 bits.
Why Databases Love Snowflake
Snowflake's biggest advantage isn't really uniqueness. It's ordering.
Random UUIDs insert like this:
A9F...
123...
FFE...
8C1...
...forcing the database to constantly rewrite parts of its index. Snowflake IDs, by contrast, arrive like this:
100001
100002
100003
100004
New rows land near the end of the index almost every time, which means:
fewer B-tree page splits
better cache locality
higher write throughput
less index fragmentation over time
Sometimes the choice of ID format is, quietly, a database performance decision.
Final Thoughts
So... generating an ID is easy.
Until it isn't.
Snowflake solves the obvious problem with an elegant design, but it also reveals a deeper truth: in distributed systems, the challenge is rarely the algorithm itself. It's building an environment where that algorithm can keep working correctly as machines, traffic, and infrastructure continue to evolve.
That's why Snowflake is still studied today. Not because packing bits into an integer is particularly difficult, but because it demonstrates a timeless engineering principle.
Sometimes the hardest part of a distributed system isn't processing billions of requests. It's simply agreeing on the next number.
Top comments (0)