SysPad › Interview practice › Design a unique ID generator

Design a unique ID generator

The interviewer says: "Design a unique ID generator for a distributed system."

Every new object, such as an order, a post or a message, needs an ID. IDs are unique, numeric and fit in 64 bits.

They sort by creation time. A year after launch the system must give out 10,000 IDs a second.

How we work it: Interview ready means finding what every ID waits on, taking it off the path, and knowing what each of the 64 bits buys.

Do the numbers before the boxes

An interviewer gives a rate, not a design. This design has a single load: IDs a second.

A 45-minute version of this answer is at the end.

The callers are the services that create objects. Each new object needs one ID, and its write waits for it.

We assume one AWS Region, us-east-1. Every count on this page is our estimate unless sourced.

At launch: 500 IDs a second at the peak and 200 on average. The peak is 2.5 times the average.

A year on, traffic is 20 times larger: 10,000 IDs a second at the peak, as the question says, and 4,000 on average.

An ID must be unique: never issued twice, through crashes, restarts and clock changes.

It must be numeric and fit in 64 bits, because it becomes a primary key.

Time-sorted means a newer ID is larger. Within one process, that always holds. Across machines, it holds when two IDs were made more than a millisecond plus their clock skew apart.

Feeds and timelines sort by it.

Every write in the company waits on an ID. So an ID needs well under a millisecond of server time, and it must keep coming through a short store outage.

Check in: Why must an ID generator be fast and hard to stop, when it only hands out numbers?

Show an answer

Every write that creates an object waits on its ID. A slow ID is a slow write, and no ID is a failed write, across the whole company.

Reading the result tiles

Each stage has a live canvas and four result tiles. Four things hold on every stage.

Load: the buttons count IDs a second, and the tile labels them req/sec.

A card turns red at 90% of its limit. A part saturates past 100%, and only then does the result name it. Otherwise the last tile notes the busiest part.

Latency is server time on the slowest path. Past saturation it is a floor, which is why it prints "≥".

Cost: each card bills its peak load all month; a beat near the end uses the average month. Where a card reads differently from AWS, the stage that shows it says so.

Check in: Stage 1's latency tile reads 4ms. Is that what a caller waits for an ID?

Show an answer

No. It is server time, 3.75 ms, printed as 4ms. A real call through the load balancer adds about 1 ms of network round trip (our estimate).

One counter item, at launch

Stage 1 is the launch: 500 IDs a second at the peak. The simplest correct answer is a counter.

A caller asks for an ID through an ALB, AWS's application load balancer. It passes the request to a stateless ID service.

The service runs on Fargate, AWS's managed containers. Each task is one container, here with 1 vCPU, one virtual processor, and 2 GB.

The counter is one item, one record, in DynamoDB, AWS's managed key-value database.

Each request is one UpdateItem call: add 1 to the stored number and return the new value.

DynamoDB applies writes to one item one at a time. So every ID is unique, and IDs rise in the order the writes commit.

A write unit is DynamoDB's unit of write capacity: one write a second, of an item up to 1 KB. The warnings call it WCU.

DynamoDB stores a table in partitions, and one item lives in one partition. AWS caps a partition at 1,000 write units a second.

The counter card counts write units, not requests. Here they are the same: 1 write an ID.

At 500 IDs a second the item carries 500 of its 1,000: 50%. The table has 1,000 write units, so it is at 50% too.

The service needs about 0.25 ms of CPU a request (our estimate): 4,000 requests a second a vCPU, 2,800 at its 70% target.

500 is under one task, so the service sits at its floor of 2 tasks.

Nothing saturates; the note reads "Busiest: Counter (one item) at 50%".

The latency tile shows 4ms: 1.5 ms at the load balancer, 0.25 ms in the service and 2 ms at the counter, 3.75 ms.

The cost tile shows $625/mo: the load balancer $85.32, the service $71.09 and the counter $468.34.

The load balancer card bills 0.6 new connections a call. Our callers are services that keep connections open, so AWS would bill by bytes: $21.14.

Press 200, the average hour: the item is at 20%, and the cost tile shows $583/mo.

The trade-off:

A write on one item for every ID. In exchange, IDs are unique, dense and strictly in order, from the simplest design there is.

Stage 1: One counter item, at launch

  • Load: 500 req/sec
  • First to saturate: Nothing at this load
  • Estimated cost: $625/mo

Check in: At what rate does this design hit a wall, and where?

Show an answer

At 1,000 IDs a second, at the counter item. Each ID is one write to one item, and one item takes at most 1,000 write units a second.

The same counter at 10,000 IDs a second

The interviewer asks: A year on: 20 times the traffic, 10,000 IDs a second.

Nothing in the design changes. The team saw throttling and raised the table from 1,000 to 14,000 write units.

The item now gets 10,000 writes a second against its 1,000. The table has room: 10,000 of 14,000, 71%.

The item does not. AWS can give one hot item a partition of its own, but a partition takes at most 1,000 write units a second.

That limit is from DynamoDB's developer guide. More write units on the table cannot help one item.

The result names the counter. Its warning says the hottest key draws 10,000 write units a second, 10 times the per-partition ceiling.

Throttling is DynamoDB refusing the excess with a throughput error. The SDK, AWS's client library, retries with backoff, then fails the call.

So about 9 IDs in 10 fail at the peak, and so does every write waiting on one.

The latency tile shows "≥ 4ms", a floor. The counter card prints 2 ms at any load, so the failures are the story, not the tile.

The cost tile shows $8,093/mo: the load balancer $1,398.60, the service $142.19 at 4 tasks, and the counter $6,552.34.

The raise costs $6,084.00 a month more and changes nothing.

Press 4,000, the average hour: the item is still 4 times its cap, and the cost tile shows $7,193/mo.

The trade-off:

One item takes every ID. Money fixes the table, not the item.

Stage 2: The same counter at 10,000 IDs a second

  • Load: 10,000 req/sec
  • First to saturate: Counter (one item)
  • Estimated cost: $8,093/mo
  • Latency past saturation is a lower bound, not an estimate. Counter (one item) is saturated on the critical path, where the queueing model stops tracking a real queue - the true figure is higher by an amount this model cannot know. Relieve the bottleneck to get a latency number worth reading.
  • Counter (one item): Hot-partition write throttling: hottest key draws 10000 WCU/s, above the 1000 WCU/s per-partition ceiling.

Check in: The table has 14,000 write units and uses 10,000. Why is it throttled?

Show an answer

Every write goes to one item, which lives in one partition. A partition takes at most 1,000 write units a second, whatever the table holds.

What a candidate tries first

Find the unit of contention before buying capacity. Here it is one item, and three fixes come up first.

On-demand mode, where DynamoDB bills each request, has the same per-partition cap. So does any number of write units.

Several counter items can split the numbers, as Flickr's two ticket servers split odd and even.

With n items, item k gives out the numbers that leave k when divided by n.

To keep each item under 800 writes a second, 80% of its 1,000, takes 13 items: 10,000 / 800 = 12.5.

Items hash to partitions, so two can start in one partition and be throttled until DynamoDB moves them apart.

It is still one write an ID. And IDs from different items interleave, so they no longer sort by creation time.

A sequence in Aurora PostgreSQL, AWS's managed relational database, is a counter the database keeps. It takes about 0.1 ms of CPU a call (our estimate).

10,000 a second is then one vCPU: a db.r6g.large writer, with 2 vCPUs, at about 50%. It works at this scale.

Its limits are one writer, one Region and a round trip an ID. The wall in Stage 2 is DynamoDB's per-item limit, not counters in general.

Step through the figure to compare a bigger table with more items.

The trade-off:

More items remove the wall and the time order with it. A database sequence keeps the order, at a round trip for every ID.

A bigger table, or more items

  1. The table's 14,000 write units spread across its partitions. The counter item lives in one partition, and a partition takes at most 1,000.
  2. 10,000 writes a second arrive at that one partition. 1,000 succeed, and the other 9,000 are throttled.
  3. 13 items take about 769 writes a second each. Items hash to partitions, so two can share one, and both are throttled until DynamoDB moves them apart. Then each is under its 1,000.
  4. But each item counts on its own, so a later object can get a smaller ID from a slower item.

Check in: Why do 13 counter items clear the wall but break a requirement?

Show an answer

Each of 13 items takes about 769 writes a second, under the 1,000 one partition gives one item.

Items hash to partitions, so two can start in one partition and be throttled until DynamoDB moves them apart. And IDs from different items interleave, so they no longer sort by creation time.

Blocks of 1,000 IDs

Stage 3 keeps 10,000 IDs a second. The counter leaves the request path.

Each service task takes a block of 1,000 numbers with one counter write: add 1,000 and return the new value, the block's last number.

The task hands out the block from memory. When a block is 10% used, it fetches the next one in the background.

That fetch is async: no request waits on it. This is the double buffer of Leaf, the ID service of Meituan, a Chinese delivery company.

The counter takes 0.001 writes an ID: 10 a second at the peak. The item is at 1%, and the table's 25 write units at 40%.

Nothing saturates, and the note names the ID service as busiest.

The latency tile shows 2ms: 1.5 ms at the load balancer and 0.25 ms in the service, 1.75 ms. The counter is off the path.

The cost tile shows $1,553/mo: the load balancer $1,398.60, the service $142.19 and the counter $12.04.

Time order now holds only within one task. At the peak a task gives out 2,500 IDs a second, so a block lasts 0.4 s.

At 200 a second on 2 tasks, a block lasts 10 s. A quiet task hands out older numbers than a busy one.

A task that stops loses the rest of its blocks, up to about 1,900 numbers. That leaves gaps, never duplicates.

If the counter is down, a task runs on what it holds: 1 to 1.9 blocks. That lasts 0.4 to 0.8 s at the peak, and 10 to 19 s at 100 IDs a second.

Blocks sized to minutes of a task's traffic would ride out longer counter outages, at the price of bigger gaps.

Press 4,000, the average hour: the counter takes 4 writes a second, 16%, and the cost tile shows $652/mo.

In the editor, the block size is the counter edge's "Traffic multiplier": 0.001 for blocks of 1,000, 0.1 for blocks of 10.

On Stage 2's canvas, blocks of 10 bring the counter to exactly its limit, and any larger block clears it, all at the same price.

The correct size depends on store outages and restart gaps, which no card models. So this page ends with a paper exercise, not a canvas challenge.

Step through the figure to see how a later ID can be smaller.

The trade-off:

IDs stay dense and numeric, and the store leaves the hot path. In exchange, time order holds only within one task.

Blocks: dense, but out of time order

  1. Each task takes a block of 1,000 numbers with one counter write, then hands them out from memory.
  2. At 10% used, a task fetches its next block in the background, so no request waits on the counter.
  3. A busy task reaches newer blocks while a quiet one stays on an older block. A later ID from the quiet task is smaller.
  4. A task that stops takes its unused numbers with it: up to about 1,900 gaps, never a duplicate.

Stage 3: Blocks of 1,000 IDs

  • Load: 10,000 req/sec
  • First to saturate: Nothing at this load
  • Estimated cost: $1,553/mo

Check in: Two objects are created a second apart, through different tasks. Can the later one get a smaller ID?

Show an answer

Yes. Each task hands out its own block. A quiet task can still be on an older block while a busy task has moved on.

Snowflake IDs, minted inside each caller

The interviewer asks: Product now relies on the order you promised: feeds sort by ID. And every write in the company waits on your service.

Blocks broke the time order by up to about two blocks' life, 0.8 s to 19 s. And the service is still a network hop on every write.

Stage 4 restores order to within clock skew, and removes the hop.

Every caller links an ID library. Snowflake is the classic layout, published by Twitter in 2010: time, a worker id and a sequence in 64 bits.

A worker id is a number no other live process holds. The library builds an ID from its clock, its worker id and a counter within the millisecond.

There is no network call. The load balancer and the ID service are gone.

Twitter ran Snowflake as a network service. Minting in the caller is this page's choice, and it saves the hop.

But every service needs the library in its language, and every process uses a worker id. The next beat takes the 64 bits field by field.

Each process gets its worker id from a lease: a claim with an end time, held as one DynamoDB item a worker id.

A process takes a free or expired id with a conditional write, a write that succeeds only if the item still matches a condition.

It renews the lease every 60 s, and each renewal sets the lease's end 10 minutes ahead.

The fence is in the IDs. A holder never mints a timestamp at or after its lease's end, and the next holder never mints one at or before it.

So old and new IDs cannot collide, whatever the clocks.

The store takes 0 writes an ID. About 300 caller processes (our estimate) renew once a minute: 5 writes a second, whatever the ID rate.

Nothing saturates; the note reads "Busiest: Worker-id leases at 10%", 5 of 50 write units at steady state.

The lease card divides its write units by writes an ID, so it calls its ceiling 100,000 IDs a second. Leases follow processes, not IDs.

Stage 4 has no load buttons for the same reason.

The latency tile shows 0ms. A mint is about 100 ns of CPU inside the caller (our estimate), and the canvas has no box for it.

The tile does not show the one thing that can still stop minting: the lease table.

The lease length is the outage budget. If the lease table is unreachable, each process mints until its lease ends: at least 9 minutes.

During an outage no new process can start minting. The beat on leases covers health checks and deploys.

The cost tile shows $24/mo: $23.74 for the lease table.

By hand, minting costs 10,000 x 100 ns = 1 ms of CPU a second across all callers, 0.1% of one vCPU.

The callers also lose a network round trip on every write, about 1 ms (our estimate).

The trade-off:

Coordination moves from every ID to every process, once a minute. In exchange, minting stops if the lease table stays unreachable past the lease length.

Stage 4: Snowflake IDs, minted inside each caller

  • Load: 10,000 req/sec
  • First to saturate: Nothing at this load
  • Estimated cost: $24/mo

Check in: Stage 4's latency tile shows 0ms. What can still stop every write in the company?

Show an answer

The lease table. If it stays unreachable longer than the lease that remains, at least 9 minutes, every process stops minting.

64 bits, field by field

Stage 4 uses the classic Snowflake layout. Each field answers one requirement, and none can change once IDs are stored.

The sign bit is always 0, so the ID stays positive in a signed 64-bit integer.

The 41 bits after it count milliseconds since our launch day, 1 January 2026. 2^41 ms is 69.68 years.

Time takes the highest bits, so a larger ID is a later millisecond.

Then 10 bits of worker id: 1,024 processes can mint at once.

The last 12 bits are a sequence within the millisecond: 4,096 IDs an ms a process, 4.096M a second.

The ID is the milliseconds x 2^22, plus the worker id x 2^12, plus the sequence.

In a new millisecond the sequence starts at 0.

Once a millisecond's 4,096 are used, the next ID waits for the next millisecond: at most 1 ms, never a duplicate.

Snowflake's own README describes that protection against rollover in the same millisecond.

Uniqueness rests on one rule: no two live processes share a worker id. The leases enforce it.

Step through the figure to build one ID.

The trade-off:

The time field sets how long IDs last, and the worker field how many processes can mint at once. Neither can change once IDs are stored.

One Snowflake ID, field by field

  1. 64 bits: a sign bit kept at 0, 41 bits of milliseconds, a 10-bit worker id and a 12-bit sequence.
  2. The ID is t x 2^22, plus w x 2^12, plus s. Time takes the highest bits.
  3. In one millisecond the sequence counts up. In a new millisecond it starts again at 0.
  4. Once a millisecond's 4,096 are used, the next ID waits for the next millisecond: at most 1 ms, never a duplicate.

Check in: A process mints its 4,097th ID in one millisecond. What happens?

Show an answer

It waits for the next millisecond, at most 1 ms. The sequence never wraps inside a millisecond, so there is no duplicate.

When the clock moves

An ID carries a timestamp, so a clock that steps back could mint the same ID twice. A senior interviewer always asks about it.

NTP, the protocol that keeps server clocks in time, can step a wall clock back.

The design's answer: read the wall clock once at start, then advance it with the monotonic clock.

On Linux, NTP slews the monotonic clock, speeding it up or slowing it a little, but never steps it. So a running process never sees time go back.

The process re-anchors only forward, at a lease renewal.

Without the anchor, wait out a small step back, up to 5 ms (our threshold).

For a larger one, fail the write and mark the process unhealthy, so its traffic goes elsewhere.

Or take a fresh worker id whose last lease ended before the current clock reading.

A step back threatens only the same worker id, because an ID is unique by worker id, millisecond and sequence.

A third answer is a logical clock that borrows future milliseconds. It never stops, but ID times run ahead of real time.

A process that restarts after its clock went back mints only timestamps after its lease's end. It cannot reissue an ID.

Leap seconds: Amazon Time Sync, AWS's time service, smears them, spreading the extra second out. So its clock never steps back.

Its precision hardware clock does not smear, and AWS says not to mix the two during a leap second.

The last leap second was at the end of 2016. In 2022 the CGPM, the treaty body for units, decided to stop them by 2035.

The trade-off:

A large step back costs a wait, a refused write or a fresh worker id. In exchange, no step can make a duplicate.

Check in: NTP steps a server's wall clock back 2 s. What does a running process with the monotonic anchor do?

Show an answer

Nothing changes. Its time comes from the monotonic clock, which is never stepped. It re-anchors only forward, at a lease renewal.

Worker-id leases and the outage budget

Two live processes with one worker id would mint duplicates. The lease table prevents that, and it is the one store minting depends on.

The lease table holds one item a worker id: its holder, and the end of its lease, which we call E. Here is one id's life.

Take. A process takes an id with a conditional write that succeeds only if the id has no holder or its E has passed.

From then on it mints only timestamps after the E it found, so its IDs never collide with the last holder's.

Mint. Before each ID it checks that the timestamp is before its own E. A pause after the check cannot matter: the timestamp is already in the ID.

Renew. Every 60 s it moves E to now plus 10 minutes, on condition that it is still the holder. A restarted process counts as a new holder, so it cannot renew its old lease.

Stop. On a clean stop, the process releases its id and clears the holder.

E moves back to just after the last ID it minted, but never before the E it found. So E always stays after every ID ever minted under that worker id.

Crash. A crashed process's id rests until its E, up to 10 minutes. The 724 spare ids, 1,024 less 300, absorb about 1.2 crashed starts a second.

Outage. If the table becomes unreachable, each process mints until its E. A renewal is at most 60 s old, so that is at least 9 minutes.

A health check is the probe that decides whether a task gets traffic. A task reports healthy only while it holds a lease, so a task without one never takes writes.

Deploys freeze during the outage. A deploy would swap tasks that hold working leases for tasks that cannot get one.

Longer outages. A longer lease rides out more, but a crashed id then rests that long.

DynamoDB's endpoint name in us-east-1 could not be resolved for 2 h 52 min on 19 to 20 October 2025, per AWS's post-event summary.

So clients could not open new connections, and no lease we would choose covers that.

Meituan's Leaf keeps a persistent worker id per host, cached on disk. It mints through any coordinator outage, and gives up the protection against two live holders.

Step through the figure to follow one worker id through an outage.

The trade-off:

One write a process a minute, and a store that minting depends on. In exchange, worker ids are reused safely, whatever the clocks.

One worker id through an outage

  1. Process A takes worker id w with a conditional write. Its lease ends at E, 10 minutes ahead.
  2. Every 60 s A renews, and E moves 10 minutes ahead. A mints only timestamps before E.
  3. The lease table becomes unreachable. A keeps minting until E, at least 9 minutes, then stops.
  4. After E, process B takes w and mints only timestamps after E. Old and new IDs cannot collide.

Check in: The lease table is unreachable for 5 minutes. What happens to minting, and to a task that starts meanwhile?

Show an answer

Running processes keep minting: each lease has at least 9 minutes to run. The new task cannot take a lease, so it fails its health check and gets no traffic.

Order, leaks and the other IDs

Four follow-ups come after the layout: how sorted the IDs are, what they give away, UUIDv7, and how they behave as keys.

Snowflake IDs are k-sorted: ordered by timestamp to within the clock skew between machines, not strictly.

Inside one millisecond they sort by worker id, not by arrival. Time Sync typically keeps skew well under a millisecond.

A strict order, such as a ledger or one conversation, needs one sequencer for each ordering scope.

An ID gives away its creation time, its worker id and, over many IDs, the issue rate.

Stages 1 to 3 give away more: exact volume, and the next object's ID. Where that matters, give objects a separate random public id.

UUIDv7 is a 128-bit ID: 48 bits of Unix milliseconds, then version and mostly random bits. RFC 9562 defined it in May 2024.

It needs no worker ids and no leases. It costs 16 bytes against 8 in every index, and InnoDB, MySQL's storage engine, copies the primary key into every secondary index.

Say: "If 128 bits is fine, UUIDv7 removes worker-id assignment. Snowflake exists for 64 bits."

Random keys, like UUIDv4, insert at random places in a B-tree index. Once it outgrows memory, inserts miss cache and leave pages part full.

Time-ordered keys append at the end of the index instead. DynamoDB hashes its partition key, so an ascending ID is fine there.

On a store split by key range, such as Google's Spanner, an ascending key sends every insert to the last range.

A JavaScript number is exact only to 2^53. These IDs pass it 24.86 days after launch day, so send them as strings.

To shard by ID, hash it. The low bits restart at 0 every millisecond, so id mod 16 skews at low traffic.

The trade-off:

A 64-bit time-sorted ID costs worker ids, leases and a leak of creation time. UUIDv7 costs 8 more bytes in every index instead.

Check in: When would you choose UUIDv7 over a Snowflake layout?

Show an answer

When keys can be 128 bits. UUIDv7 needs no worker ids and no leases. Snowflake exists for 64-bit keys.

IDs across Regions

The interviewer asks: The company now runs in three Regions. How do IDs stay unique?

Minting must never wait on another Region. So the lease tables stay per Region, one in each.

A DynamoDB global table, one table copied across Regions, resolves concurrent writes in its default mode by last writer wins.

Then two Regions could both take one worker id, and their processes would mint duplicates.

Its strong-consistency mode, generally available since mid-2025, fails a conflicting write, so a take there is safe.

But every take and renewal would wait on another Region, and a Region cut off from the others could not renew at all.

Per-Region tables avoid both. Two Regions must still never hand out the same worker id; Your turn asks how.

Order across Regions holds only to their clock skew.

Step through the figure to compare one global lease table with one table per Region.

The trade-off:

Each Region takes and renews leases alone, with no call to another. In exchange, nothing checks across Regions that two never hand out the same worker id.

One global lease table, or one per Region

  1. One global table in its default mode: two Regions take worker id w at the same moment, and the last writer wins.
  2. Both Regions believe they hold w. Their processes mint IDs with the same worker id: duplicates.
  3. Strong-consistency mode fails one of the two takes. But every take and renewal now waits on another Region.
  4. One lease table per Region: no take or renewal crosses a Region, and a cut-off Region keeps renewing.

Check in: Why not one global lease table for all three Regions?

Show an answer

In its default mode a global table lets the last writer win, so two Regions could take one id. Strong consistency is safe, but every renewal waits on another Region.

The numbers on one screen

Skip on a first read; come back before the interview. Every figure on Stages 1 to 4 comes from the one before it.

Load balancer: an LCU is its billing unit. It bills the largest of four measures, such as 25 new connections a second or 1 GB processed an hour.

The card bills 0.6 new connections a call. At 500 IDs a second: 500 x 0.6 / 25 = 12 LCU.

An LCU costs $5.76 a month ($0.008 an hour), plus $16.20 for the hours: $16.20 + 12 x $5.76 = $85.32.

Service: 0.25 ms of CPU a request, so a 1-vCPU task serves 4,000 a second, 2,800 at its 70% target.

A task costs $0.04937 an hour. 500 / 2,800 is under one task, so the floor of 2 tasks: $71.09 a month.

Counter: a write unit costs $0.00065 an hour, $0.468 a month. 1,000 x $0.468 = $468.00, plus $0.34 for one read unit and 1 GiB.

Stage 1's three lines round to the tile's $625.

Stage 2 at 10,000: 10,000 x 0.6 / 25 = 240 LCU, $16.20 + 240 x $5.76 = $1,398.60.

Service: 10,000 / 2,800 = 3.6, so 4 tasks, $142.19. Counter: 14,000 x $0.468 = $6,552.00, plus $0.34.

Stage 2: $1,398.60 + $142.19 + $6,552.34 = $8,093.13, the tile's $8,093.

Stage 3: the same load balancer and service. Counter: 25 x $0.468 = $11.70, plus $0.34: $12.04.

Stage 3: $1,398.60 + $142.19 + $12.04 = $1,552.83, the tile's $1,553.

Stage 4: 300 processes / 60 s = 5 writes a second, 10% of 50. 50 x $0.468 = $23.40, plus $0.34: $23.74.

Sizes here are binary: KiB and GiB, with GiB being what AWS bills as GB.

In an average month, at 4,000 IDs a second, AWS bills the load balancer by bytes, because callers keep connections open.

4,000 x 0.5 KiB x 3,600 s = 6.87 GiB an hour, so 6.87 LCU: $55.75.

Stage 3's average month is about $139 with 2 tasks all month, and up to about $210 with peak hours at 4.

Stage 4 stays $23.74. Data transfer is in neither.

Stage 4 saves about $115 to $186 a month. The real gain is a network hop and a dependency off every write.

Check in: Close the page and rebuild Stage 2's bill from 10,000 IDs a second.

Show an answer

Load balancer: 10,000 x 0.6 / 25 = 240 LCU, $16.20 + 240 x $5.76 = $1,398.60. Service: 4 tasks, $142.19.

Counter: 14,000 x $0.468 = $6,552.00, plus $0.34. Total: $8,093.13.

Say this in 45 minutes

The interviewer asks: What fails in this design, and what does a caller see?

A design that works on a good day is half an answer. The interviewer asks what fails, who notices, and what it costs.

The lease table is unreachable: processes keep minting until their leases end, at least 9 minutes. New tasks fail their health checks, and deploys freeze.

It stays down past the lease: minting stops, and so does every write that needs an ID. A longer lease, or Leaf's per-host ids, buys more time.

A process crashes: its worker id rests until its lease ends, up to 10 minutes. No ID repeats.

NTP steps a clock back: a running process never sees it. A restarted one mints only timestamps after its lease's end.

In Stage 3, the counter is down: tasks run on their buffered blocks, 0.4 to 0.8 s at the peak.

At 10 times, 100,000 IDs a second: Stage 2's counter would be 100 times its item.

Stage 3 would send 100 counter writes a second, so raise the table to about 150 write units. Its service needs 36 tasks, past the cap of 20.

Stage 4 changes nothing per ID. But 3,000 processes outgrow its 1,024 worker ids, and Your turn sizes that field.

They also renew 50 times a second, which fills the 50 write units. Raise the table to about 340, with room for deploys.

At 340 write units, the 50 renewals use 14.7%, and the table costs $159.46 a month.

Say it out loud: "One counter item caps at 1,000 writes a second. Blocks take it off the path, but lose time order."

And: "Snowflake mints in the caller with no network call. The lease length is the outage budget."

Minutes 0 to 5: the requirements, unique, numeric, 64-bit and time-sorted, at 10,000 a second.

Minutes 5 to 12: Stage 1, the counter, and Stage 2, the one-item wall.

Minutes 12 to 18: Stage 3, blocks, and what they cost in order.

Minutes 18 to 30: Stage 4, the layout, clocks, worker-id leases and the outage budget.

Minutes 30 to 40: leaks, UUIDv7, index locality, JavaScript and sharding.

Minutes 40 to 45: other Regions, and 10 times the traffic.

The trade-off:

Each step has a price: a write an ID, then lost time order, then leases with an outage budget. Each price buys the next requirement.

Check in: Set a timer for 45 minutes. Give the whole answer out loud, and check it against the pacing plan.

Say it out loud

What load sizes this design?
IDs a second: 10,000 at the peak a year on, 4,000 on average. 500 at the launch peak.
Why is one counter item a wall?
Every ID is a write to one item. One item lives in one partition, and a partition takes at most 1,000 write units a second.
Why did 14,000 write units not help?
The table's units spread across partitions. The one item can use only its own partition's 1,000.
What do blocks of 1,000 buy, and what do they cost?
Counter writes fall to 0.001 an ID, off the request path. Time order holds only within one task, and a stopped task leaves gaps.
What is in a classic Snowflake ID?
A sign bit kept at 0, 41 bits of milliseconds, a 10-bit worker id and a 12-bit sequence.
What happens when a millisecond's sequence is used up?
The next ID waits for the next millisecond: at most 1 ms, never a duplicate.
How do two holders of one worker id never collide?
The fence is in the IDs. A holder never mints a timestamp at or after its lease's end; the next holder never mints one at or before it.
What is the outage budget?
The lease length. With 10-minute leases renewed every 60 s, minting survives a lease-store outage of at least 9 minutes.
Why tie a task's health check to its lease?
A task without a lease cannot mint. Failing its health check keeps writes away from it.
How does a running process avoid a clock step back?
It reads the wall clock once at start, then advances it with the monotonic clock, which is never stepped.
Are Snowflake IDs strictly ordered?
No. Across processes they are ordered to within clock skew. A strict order needs one sequencer for each ordering scope.
When is UUIDv7 the better answer?
When 128 bits is fine: it needs no worker ids and no leases. Snowflake exists for 64 bits.
Why send IDs to a browser as strings?
A JavaScript number is exact only to 2^53, and these IDs pass it 24.86 days after launch day.

Check yourself

Stage 2: the table has 14,000 write units, and the counter takes 10,000 writes a second.

Why is it throttled?

Stage 3 at 200 IDs a second: 2 tasks, each handing out blocks of 1,000.

Do the IDs sort by creation time?

A Stage 3 task crashes holding about 1,900 unused numbers.

What happens to those numbers?

A Stage 4 process mints its 4,097th ID inside one millisecond.

What happens?

The lease table is unreachable for 5 minutes.

What happens to minting?

DynamoDB's us-east-1 endpoint name cannot be resolved for 2 h 52 min, as in October 2025. Leases last 10 minutes.

Does minting survive?

A new task starts while the lease table is unreachable.

What should it do?

NTP steps a server's wall clock back 2 s while a process runs.

What does the process with the monotonic anchor mint?

Your primary keys can be 128 bits.

Which ID do you pick?

A web client reads the IDs as JSON numbers.

When does it go wrong?

Your turn

Do this one on paper: no canvas has a field for it. The two buttons are for a second exercise: rebuild Stages 1 to 4 from memory.

Stage 4 used the classic layout for 300 processes. Your company now adopts the ID library everywhere. Design its 64-bit layout.

IDs must fit a signed 64-bit integer and stay positive.

Their timestamp counts milliseconds, as Stage 4's does, so they sort by creation time just as Stage 4's IDs do.

They must last 40 years from launch on 1 January 2026.

A batch insert can mint up to 400 IDs back to back in one process, within one millisecond. It must never wait for the sequence.

At the planned peak, 3,000 processes run in 3 Regions; the largest Region has 1,500.

ECS is the AWS service that runs Fargate tasks.

Deploys use ECS's default rolling update, which starts the new tasks before it stops the old ones.

A platform release (a base-image patch, say) can redeploy every service at once. A base image is the shared image every service builds on.

You cannot change how services deploy, and a release may not be staggered or slowed.

How many bits does each field get? When do the IDs run out? What happens to the next ID in a millisecond once the sequence is used up?

Each Region keeps its own lease table: how do the three Regions avoid handing out the same worker id?

This page prints no answer. Check each field against the worst moment the question allows.

That is the most IDs in one millisecond, the latest date, and the most processes alive at once. Then add up the bits.

Open the last stage in the editor and change it. Or start from an empty canvas and build the design from memory.

Lessons for this problem

The finished architecture

A complete design of this problem in the example gallery. It may size some parts differently.

Previous: Design a key-value store · Next: Design a news feed

All interview practice · Lessons · Open the SysPad canvas