An interviewer asks you to design an ID generator. You propose UUIDs, then get three follow-ups: can a mobile client generate IDs offline, will sorting them reproduce the order of accepted writes, and what happens when a timed-out create request is retried? A format name answers only part of the problem.

For a senior or staff engineer, this is a useful design exercise because a small component exposes larger contracts: who owns identity, where coordination happens, and what the database actually enforces. This guide uses a hypothetical document service to connect those contracts. Its workload numbers and rehearsal rubric are illustrative practice tools, not company interview criteria or measured performance results.

1. Define the identity contract before choosing bits

Start with the object. Our practice service lets users create documents from several regions and sometimes while disconnected. Each document needs a stable identity that survives retries, replication, and tenant moves. Ask whether uniqueness means one table, one tenant, one deployment, or every independently operating installation that may later merge data.

Then separate four requirements: unique identity, approximate chronological sorting, strict operation ordering, and resistance to guessing. Ask whether IDs must fit an existing integer column, appear in URLs, or be allocated before contacting a server. A requirement for invoice numbers without gaps is a different business workflow from assigning internal document keys.

Write the invariant in plain language: two accepted documents must never share the same identity within the declared scope. Treat a generator as a proposal mechanism and identify the authority that accepts the proposal. PostgreSQL's constraint documentation describes unique constraints and primary keys; a primary key combines uniqueness with non-nullability. If a globally unique identity is required across independent shards, a constraint on each shard alone does not enforce that global invariant.

2. Compare a database sequence, UUIDv7, and worker IDs

A sequence is a strong baseline when the database owns creation

For a service with one authoritative database, a sequence can be the simplest allocation authority. PostgreSQL documents atomic, multiuser-safe nextval calls, but allocated values are not reclaimed after transaction aborts. Gaps therefore do not establish that records were deleted. Nor does allocation order establish commit order: one transaction can obtain a number and pause while a later transaction finishes.

Explain the availability tradeoff. If clients need an ID before they can contact that authority, a database sequence cannot satisfy the requirement by itself. Allocating blocks to workers reduces calls, but introduces block ownership, unused ranges after crashes, and recovery rules. Make that complexity earn its place; a central service is not automatically necessary just because the prompt says distributed.

UUIDv7 supports decentralized generation with a time prefix

RFC 9562 defines UUIDv7 as a 128-bit identifier with a 48-bit Unix-millisecond timestamp prefix. Excluding version and variant fields, 74 bits remain for randomness or permitted combinations of sub-millisecond time, counters, and randomness. Do not describe every implementation as having 74 independent random bits. The format does not turn independent clocks into a global ordering authority.

For the document service, propose a maintained UUIDv7 implementation when offline creation and roughly time-oriented keys matter. Ask how it handles the same millisecond, clock rollback, concurrency, and process restart. PostgreSQL 18 provides uuidv7() and timestamp extraction, while warning that the extracted timestamp need not exactly equal generation time. Check the deployed version rather than assuming every database has the same functions.

Worker-based IDs exchange randomness for allocation discipline

The historical Twitter Snowflake implementation combines elapsed milliseconds, datacenter and worker identifiers, and a per-millisecond sequence. Its layout uses five datacenter bits, five worker bits, and twelve sequence bits. The remaining timestamp budget is 41 bits beneath the sign bit. This is an archived implementation to study, not a recommendation to deploy retired code.

The interesting design question is who assigns worker identities and prevents overlap during restarts or partitions. Two active generators using the same location identity and timestamp can produce duplicates. Propose durable allocation and a rule that stops an old worker before its identity is reused. Merely naming a lease does not prove the old process has stopped generating valid IDs.

3. State exactly what sorting promises

Walk through a concrete counterexample. Region A generates a smaller document ID and then stalls. Region B generates a larger ID and commits first; A commits afterward. Sorting those keys puts A before B despite the opposite commit order. For a user-facing document list, define whether the order follows client creation time, server acceptance time, or another explicit product rule.

If the requirement is a strict sequence of accepted operations, introduce an ordering authority such as a committed log position and define its scope. Google's Spanner documentation explains external consistency in terms of transaction serialization and real-time order. That is a database protocol guarantee. Adding a timestamp prefix to a locally generated ID does not provide it.

Be equally precise about monotonic libraries. The ULID specification describes a 48-bit timestamp, 80-bit randomness field, and a monotonic factory that increments the random component within a millisecond. That factory's state does not coordinate every generator in a fleet. Ask about its concurrency and restart behavior before promising even a process-local ordering property.

For pagination, use an explicit order and a unique tie-breaker, then define how newly visible or late-arriving records behave between pages. If consumers need every committed change, give them a durable change position rather than asking them to poll for IDs greater than the largest one previously observed. An earlier allocated ID may become visible later.

4. Put collisions, clock rollback, and exhaustion on the diagram

Randomness needs an implementation contract. A predictable seed reused across processes can defeat assumptions about independent generation. Use a library backed by an appropriate system randomness source; Go's crypto/rand documentation, for example, identifies its generator as cryptographically secure and its shared reader as safe for concurrent use. Review the chosen UUID library's integration rather than inventing a custom generator during the interview.

Keep a uniqueness check at the authoritative write boundary. A detected conflict must not silently overwrite an existing document. For a new server-owned identity, a bounded regeneration attempt may be reasonable; for an identity already exposed to an offline client, silently substituting another value can break references. Return or reconcile an explicit conflict and preserve the original relationship between the client operation and its document.

Calculate a ceiling, then explain what it excludes

Illustrative calculation: a twelve-bit sequence has 212 = 4,096 distinct values per millisecond for one worker identity. Multiplying by 1,000 gives 4,096,000 values per second only if every millisecond's range is fully usable. That is a bit-budget ceiling, not a benchmark, service-level objective, or sustainable throughput claim.

The archived Snowflake code waits for a later millisecond when the sequence wraps and rejects a clock that moves backward. Discuss what that means operationally: generation can stall or fail, and callers need deadlines and backpressure. Test a frozen clock, a backward step, a restart with reused worker identity, and a burst that consumes the local sequence range. Never wrap a counter and knowingly emit an old value.

Define useful signals: generation latency, clock-rollback failures, sequence exhaustion, duplicate constraint violations, and active worker allocation conflicts. A finite test that finds no duplicates is evidence about that execution. It cannot prove the generator remains correct under every clock and lifecycle failure.

5. Check storage placement and wire representation

A time prefix changes where writes land. Ask how the storage engine partitions its keyspace before claiming that sequential keys are faster. Spanner's schema guidance warns that monotonically increasing leading keys can concentrate inserts in one key range. Better locality in one index and even write distribution across a range-partitioned database are different goals.

For our practice service, keep document identity separate from physical placement. A tenant or hash prefix might distribute writes, while a separate index supports listing documents by accepted time. Defend the extra index with the query it serves. Measure write distribution, index size, and listing latency on the actual engine; do not import another system's benchmark as a universal UUIDv7 speedup.

Trace an ID through the client, JSON, database driver, logs, and exports. The ECMAScript specification limits safe integer representation in Number to 253 − 1. A larger integer ID can be rounded if a JavaScript client parses it as a Number. Define an exact string representation at the API boundary and test round trips, including clients that serialize large integers differently.

Also choose canonical parsing rules. Decide whether alternate capitalization or encodings represent the same identifier, reject malformed input, and compare normalized values consistently. An ID that survives the database but changes in a spreadsheet export or browser is still a broken identity contract.

6. Separate object identity from retry identity

A fresh unique ID on every attempt can still create duplicate documents. Suppose the first create committed, its response was lost, and the client retries with a newly generated ID. Both records may satisfy every uniqueness constraint. The missing guarantee is recognizing repeated attempts at the same logical operation.

Following the caller-token pattern described in the AWS Builders’ Library, propose a stable idempotency key scoped to the authenticated tenant and operation, with a request fingerprint and a durable result mapping. For the practice design, atomically record that mapping with document creation. Concurrent attempts using the same key must converge on one accepted result; reusing the key for a different payload should produce a defined conflict.

Stripe's idempotency documentation provides a concrete API example: for API v1 it retains the first executed request's status and body, compares parameters on key reuse, and permits pruning after at least 24 hours. Those are provider-specific semantics. Choose retention for your own retry window and explain what happens after the mapping expires.

Finally, public IDs must not become authorization tokens. OWASP's IDOR prevention guidance treats complex identifiers as defense in depth and still requires access checks for each object. In the document service, validate tenant ownership on every read and write, even when the caller supplies a hard-to-guess identifier.

7. Rehearse the decision and its failure boundary

Use this illustrative practice rubric: can you name the uniqueness scope, distinguish allocation from commit order, explain generator restart behavior, trace exact serialization, and reconcile an ambiguous retry? Score each explanation as missing, partial, or concrete. The purpose is to find gaps in your reasoning, not to predict an employer's hiring decision.

In a mock interview, begin with one baseline: a database sequence when the database owns allocation, or a maintained UUIDv7 generator when independent creation matters. Then change one assumption at a time. Add disconnected clients, require integer compatibility, freeze the clock, duplicate a worker identity, and lose the response after commit. Explain which guarantee survives and which component must change.

For related preparation, revisit the time and causal-order guide and the retries and idempotency guide. Keep the document's identity on the whiteboard throughout the exercise. Follow it from allocation to durable acceptance, through a failed response, and back through a retry. That makes the format choice part of a defensible system design.

Practice explaining your design decisions

Use Interview Copilot to prepare for technical interviews and rehearse the tradeoffs behind ID generation, ordering, and retry safety.

Start preparing for free