System Design Interview: Unique Identifier for Distributed Systems

Published on 01 Aug 2026
system design interview

Almost every application needs a way to identify its data. Users, orders, payments, messages, posts, and files all require identifiers that remain unique throughout their lifetime.

Generating an identifier is straightforward when one database controls every write. The database can increment a sequence and assign the next available number. The problem becomes more difficult when records are created simultaneously across many servers, databases, and geographic regions.

In this post, we’ll design a distributed unique identifier generator. The solution will produce compact, time-sortable identifiers without requiring every request to pass through a single database.


Introduction

A system design interview may begin with a short prompt:

Design a service that generates globally unique identifiers.

The problem sounds simple, but the word “globally” introduces several questions.

How many identifiers must the system generate each second? Must they be numeric? Should newer identifiers sort after older ones? Can clients generate them locally, or must they call a service? What happens if two generator nodes accidentally receive the same identity? What happens when a server’s clock moves backwards?

There is also an important difference between uniqueness and ordering. A UUID can provide extremely strong uniqueness, but random UUIDs are not naturally ordered by creation time. A database sequence produces perfectly ordered values but can become a central dependency.

A good design begins by determining which properties the application actually requires.


Understand the Requirements and Scope

The primary functional requirement is that every successful request returns an identifier that has never previously been issued and will not be issued again.

For this design, the identifier should fit inside a signed 64-bit integer. This makes it compact and convenient for use as a primary key in databases, messages, logs, and APIs.

The identifiers should be roughly ordered by creation time. This is useful when records are indexed by identifier because newer records are inserted near one another rather than at random positions throughout the index.

Rough time ordering does not mean that the system guarantees one perfect global sequence. An identifier created in one region may occasionally appear before an identifier created slightly earlier in another region. Providing strict global ordering would require significantly more coordination.

The service should support high throughput and low latency. It should continue generating identifiers when individual nodes fail and should avoid a central database call for every request.

The initial scope does not include storing the objects associated with the identifiers. The generator creates identifiers but does not provide a lookup service for users, orders, or other business records.

The identifiers are also not intended to be security tokens. Their uniqueness does not make them secret or impossible to predict.


Estimate the Scale

Suppose the wider platform needs to generate one million identifiers per second at peak traffic.

If every request had to update one shared database row, that database would become both a throughput bottleneck and a single point of failure. Even if the database could handle the average load, network latency and cross-region coordination would make the design difficult to scale.

The service may run hundreds of generator nodes across several regions. Each node must therefore be able to generate thousands of identifiers per second without communicating with other nodes for every identifier.

A 64-bit number provides enough space to encode several pieces of information. Part of the identifier can represent time, another part can identify the generator node, and the remaining part can distinguish identifiers created by that node during the same unit of time.

This approach distributes the work while preserving uniqueness.


Consider the Main Approaches

Before selecting a design, it is useful to consider the common alternatives.

Database Sequences

A relational database can generate an incrementing identifier whenever a row is inserted. This is simple, strongly ordered, and works well when all writes use one database.

At larger scale, the sequence becomes a central dependency. Multiple database clusters may allocate overlapping values unless they coordinate, while cross-region requests add latency.

Range allocation can improve this approach. A central service assigns each database or application server a block of identifiers, allowing it to issue values locally until the block is exhausted. This reduces coordination but can create gaps when a server fails before using its entire range.

Random UUIDs

A version 4 UUID contains enough random data to make collisions extraordinarily unlikely. UUIDs can be generated independently by any client without central coordination.

Their disadvantages are size and ordering. A UUID normally occupies 128 bits, and random values are distributed throughout a database index. This can increase storage requirements and reduce index locality.

Time-ordered formats such as UUID version 7 and ULID improve ordering while retaining decentralised generation. They are strong choices when a 128-bit or string identifier is acceptable.

Hash-Based Identifiers

An identifier can be produced by hashing data associated with the object. This can be useful when identical content should receive the same identity.

For ordinary database records, however, the input may not be unique or available at creation time. Hashes are also usually larger than a compact numeric identifier, and collision handling may still be necessary.

Time and Worker-Based Identifiers

A Snowflake-style identifier combines a timestamp, worker identifier, and per-time-unit sequence number into one 64-bit value.

Each worker generates identifiers locally. As long as worker identifiers do not overlap and clocks are handled safely, the system can provide high throughput without coordinating every request.

This is the approach we will use.


Define the Identifier Format

The 64-bit identifier can be divided into several sections:

0 | 41-bit timestamp | 10-bit worker ID | 12-bit sequence

The first bit remains zero so that the value fits inside a signed 64-bit integer.

The 41-bit timestamp stores the number of milliseconds since a custom epoch. A custom epoch might be the date on which the system was first deployed rather than January 1970. Forty-one bits provide approximately 69 years of millisecond timestamps.

The 10-bit worker identifier supports up to 1,024 active generator identities. These identities can be allocated across regions and availability zones.

The 12-bit sequence supports 4,096 identifiers per millisecond on each worker. A single worker can therefore generate more than four million identifiers per second under ideal conditions.

The identifier can be assembled using bit operations:

id = (timestamp << 22) | (workerId << 12) | sequence

Because the timestamp occupies the most significant section, identifiers are approximately ordered by creation time.

The capacity of each section is a design decision. A platform requiring more workers could allocate additional bits to the worker identifier, while one requiring greater per-node throughput could allocate more bits to the sequence.


Define the API

Applications can access the generator through a small internal API:

POST /ids
POST /ids/batch

A single request returns one identifier:

{
  "id": 721864159923240960
}

A batch request returns several identifiers at once. This is useful for applications that need to create many records and want to reduce network overhead.

The service can also be packaged as a trusted library running inside other services. Local generation avoids a network request, but distributing the logic as a library makes worker allocation, clock handling, and upgrades more difficult to control.

A dedicated service provides more consistent management and observability. Applications can call a nearby regional instance, while the generator nodes still create identifiers locally without coordinating for every request.


A High-Level Design

The system contains regional generator clusters and a smaller coordination layer:

Application Services
          │
          ↓
   Regional Load Balancer
          │
     ┌────┼────┐
     ↓    ↓    ↓
 Generator Nodes
     │    │    │
     └────┼────┘
          ↓
 Worker ID Coordinator
          │
          ↓
  Strongly Consistent Store

Application services send requests to the closest regional load balancer. The load balancer distributes those requests across healthy generator nodes.

Each generator node has a unique worker identifier. Once a node has acquired that identity, it can generate identifiers in memory using its clock and sequence counter. It does not need to write each generated value to a database.

A Worker ID Coordinator assigns worker identifiers using leases stored in a strongly consistent system. The lease prevents two active nodes from using the same worker identifier at the same time.

The coordination layer is not involved in the normal request path. If it experiences a temporary failure, a generator can continue serving requests while its existing lease remains valid. It must stop before the lease expires if it cannot renew it safely.

Generator nodes are spread across availability zones so that the failure of one server or zone does not stop identifier generation in the region.


Request Flow

When a generator node receives a request, it reads the current timestamp in milliseconds and compares it with the timestamp used for the previous identifier.

If the timestamp has advanced, the node resets its sequence counter to zero. If the timestamp is unchanged, it increments the sequence.

When the sequence reaches 4,095, all values available for that millisecond have been used. The node waits until the clock moves into the next millisecond before generating another identifier.

The node then combines the timestamp, worker identifier, and sequence into the final 64-bit value and returns it to the caller.

No network coordination is required during this process. Under normal conditions, generating an identifier is an in-memory operation.


Deep Dive

The most important challenges are worker identity, clock behaviour, ordering, and recovery from failures.

Worker ID Allocation

The worker identifier must be unique among all active generator nodes. If two nodes use the same worker identifier during the same millisecond, they could produce identical values.

Worker identifiers should therefore not be assigned using a random number or a local configuration file without central control.

When a generator starts, it requests an identity from the Worker ID Coordinator. The coordinator creates a time-limited lease in a strongly consistent store. The generator renews that lease periodically while it remains healthy.

If the generator shuts down or loses its lease, the worker identifier can eventually be reassigned. The coordinator should leave a safety period before reuse so that an old node cannot continue issuing identifiers while a replacement begins using the same identity.

A node that cannot confirm ownership of its lease must stop generating identifiers. Preserving uniqueness is more important than keeping one uncertain node available.

The worker identifier may encode both a region and a node number. Alternatively, the coordinator can allocate values from one global pool. Encoding a region makes operational debugging easier but reduces the number of nodes available within each region.

Clock Moving Backwards

The design depends on timestamps, but physical clocks are not perfectly reliable. Network time synchronisation, virtual-machine migration, or hardware problems can cause a system clock to move backwards.

If a node simply accepts the earlier time, it may reuse a timestamp and sequence combination it has already issued.

For a very small rollback, the generator can pause until the clock catches up with the last timestamp it used. This creates a short latency increase but preserves uniqueness.

For a larger rollback, waiting may be unsafe or impractical. The node should mark itself unhealthy and stop accepting traffic. It can resume only after its clock has recovered or after it receives a new worker identity with appropriate safeguards.

The service should monitor the difference between local time and trusted time sources. A node with significant clock drift can be removed before it creates a problem.

A monotonic clock is useful for measuring elapsed time, but it cannot directly replace wall-clock time in the identifier because the generated timestamp must remain meaningful across restarts and machines. The implementation must carefully combine reliable wall-clock checks with monotonic elapsed-time measurements.

Ordering Guarantees

Within one generator node, identifiers can be strictly increasing. The sequence counter orders values created during the same millisecond, and the node never allows its timestamp to move backwards.

Across several nodes, identifiers are only approximately ordered. Small differences between clocks can cause a later request on one node to receive a lower identifier than an earlier request on another node.

This is acceptable if the requirement is time-sortable identifiers rather than a perfect global order.

If the business requires strict ordering across all writers, the architecture must introduce coordination. A single sequencer or consensus group could allocate the next number, but this would increase latency and reduce availability.

The interview should make this trade-off explicit. Distributed uniqueness is relatively inexpensive; strict global ordering is not.

Sequence Exhaustion

A worker can produce 4,096 identifiers within one millisecond. If demand exceeds that limit, it must wait for the next millisecond.

Waiting briefly provides backpressure and preserves the identifier format. If sequence exhaustion happens frequently, more generator nodes can be added or additional bits can be allocated to the sequence.

Batch generation requires particular care. A batch may cross a millisecond boundary or exhaust the current sequence range. The generator should divide the batch across timestamps rather than producing invalid or duplicated values.

Generator Restarts

A restarted node must not immediately reuse a worker identifier if there is any possibility that an earlier instance is still running.

The leasing mechanism prevents this by associating each assignment with an owner and an expiry time. A new process must acquire a valid lease before serving traffic.

The node should also retain or recover the last timestamp it used when appropriate. If the machine restarts with a clock earlier than that timestamp, it must wait or remain unavailable rather than risk reusing an earlier identifier range.

Containers and autoscaling environments make this especially important because generator instances may be created and destroyed frequently.


Availability and Failure Handling

Generator nodes are stateless apart from their in-memory timestamp, sequence, and worker lease. If one node fails, the load balancer can redirect new requests to the remaining nodes.

Clients should use short timeouts and may retry failed requests against another node. A retry normally produces a different identifier. This is harmless if the first response was never used, but it can create unused values. Gaps are acceptable because identifiers are unique labels, not a count of successfully created records.

If a caller requires the same result after a retry, it can provide an idempotency key. The service would then need to store a temporary mapping between that key and the generated identifier. This adds state and storage overhead, so it should only be introduced when required.

The coordination store should be replicated using a consensus protocol because issuing the same worker identity twice would threaten uniqueness. The generator cluster can tolerate temporary coordinator failures while existing leases remain valid, but new nodes will not be able to start.

This is an intentional trade-off: temporary reduction in capacity is preferable to issuing duplicate identifiers.


Multi-Region Design

Each region should contain enough generator nodes to handle its own traffic. Applications call the nearest healthy region, avoiding the latency of a cross-region request for every identifier.

Worker identifiers must remain unique across regions. One option is to reserve part of the 10-bit worker field for a region identifier and the remainder for a node identifier.

For example, five bits could represent a region and five bits could represent a worker within that region. This supports 32 regions with 32 generator identities in each region.

Another option is to allocate all 1,024 worker identifiers from a globally coordinated pool. This provides more flexibility but makes allocation dependent on a global coordination system.

The correct choice depends on the number of regions, expected generator nodes, and desired operational simplicity.

If a region becomes unavailable, applications can fail over to another region. The destination region uses its own worker identities, so identifiers remain unique without transferring the failed region’s leases.


Database and Index Behaviour

Time-ordered identifiers provide useful database properties.

Random identifiers tend to insert new records throughout a database index. This can cause frequent page splits and reduce cache locality. Time-sortable values usually place recent inserts near the end of the index, making writes more sequential.

However, purely sequential primary keys can create a hot database partition if records are partitioned by identifier range. All new writes may be directed to the partition containing the newest range.

The identifier can still be used as the primary key while the data is distributed using another key, such as customer ID, account ID, or a hash of the identifier.

The ID format should not be expected to solve the database’s partitioning strategy by itself.


Security and Privacy Considerations

A Snowflake-style identifier exposes some information about when it was created and potentially which worker or region generated it.

This is often acceptable for internal database keys, but it may not be appropriate for public identifiers. Sequential or time-based values can also make it easier to estimate system activity or enumerate nearby resources.

Authorisation must never depend on an identifier being difficult to guess. Every request should verify that the caller is permitted to access the requested object.

If exposing creation time or infrastructure information is undesirable, the public API can use a separate opaque identifier. Another option is to encode or transform the internal value before exposing it, provided the transformation is reversible and does not introduce collisions.

Security tokens, password-reset links, and session credentials require cryptographically secure randomness. They should not use the distributed identifier format described here.


Observability

The system should monitor both request performance and the conditions that could threaten uniqueness.

Important measurements include identifiers generated per second, sequence exhaustion, request latency, error rates, clock drift, clock rollback events, lease-renewal failures, and the number of available worker identifiers.

Each generated identifier does not need to be written to a log. Logging millions of values would create unnecessary cost and could become a bottleneck.

Instead, generator nodes can publish aggregated counts and operational events. Lease acquisition, lease loss, clock rollback, sequence exhaustion, and node shutdown should produce detailed audit records.

Alerts should be raised before a worker lease expires or the supply of available worker identifiers becomes critically low.


Present Your Solution and Wrapping Up

During the interview, begin by clarifying whether the system requires uniqueness, time ordering, strict global ordering, unpredictability, or all of these properties. These requirements lead to very different designs.

Explain why a central database sequence and random UUIDs may not meet every requirement. Then introduce the Snowflake-style format containing a timestamp, worker identifier, and per-millisecond sequence.

Present the high-level architecture before discussing bit allocation. Applications send requests to regional generator clusters, while a strongly consistent coordinator assigns worker identities through leases. Once a node has a valid identity, it generates identifiers locally without database access.

Walk through the normal request flow and then focus on the main failure cases. Explain what happens when the sequence is exhausted, a worker restarts, its lease expires, or its clock moves backwards.

Be precise about the guarantees. The system produces globally unique, approximately time-ordered identifiers. It does not guarantee gap-free values or one strict order across every region.

Finish by connecting the architecture back to the original requirements. It provides high throughput and low latency because the normal generation path is local. It remains available when individual nodes fail, while the coordination layer protects the uniqueness of worker identities.


Further Improvements

The system can support multiple identifier formats for different workloads.

UUID version 7 may be offered when clients require decentralised generation and can accept a 128-bit value. Shorter encoded identifiers may be provided for URLs or customer-facing references. Completely random identifiers can be used when predictability is a concern.

Worker allocation could be integrated with the deployment platform so that nodes automatically acquire and release identities as they scale. Regional capacity can also be adjusted according to observed traffic.

A logical-clock component could provide more predictable ordering when physical clocks drift, although it would add state and complexity. Systems requiring strict global ordering could use a separate coordinated sequencer for only those operations that genuinely need it.

The bit allocation should be reviewed before the timestamp range approaches its limit. A version field or migration strategy may be introduced if the identifier format is expected to change during the lifetime of the platform.

Finally, client libraries can make adoption easier by handling retries, batch requests, validation, and conversion between numeric and string representations consistently.


Summary

A distributed identifier generator must provide uniqueness without turning one database or server into a global bottleneck.

The proposed design uses a 64-bit Snowflake-style identifier containing a millisecond timestamp, worker identifier, and sequence number. The timestamp makes values approximately sortable, the worker identifier separates generator nodes, and the sequence allows one node to create many values during the same millisecond.

Applications send requests to nearby regional generator clusters. Each node acquires a unique worker identity through a strongly consistent lease and then creates identifiers using only local memory and its clock.

The most important failure cases involve duplicated worker identities and clocks moving backwards. Leases, safety periods, clock monitoring, and fail-closed behaviour protect the uniqueness guarantee.

The system deliberately does not provide gap-free identifiers or a strict global order. Those guarantees would require more coordination and would reduce scalability and availability.

A strong system design answer makes these guarantees and trade-offs explicit. Generating a number is easy; generating it safely across thousands of machines is the real design problem.