</> MAANG.io
system design · 301

Advanced

Master system design interviews with scalable architecture patterns, distributed systems, and real-world design challenges.

0/480 solved 0% complete

Section 2: The Contract a Duplicate Would Break - Requirements (Functional & Non-Functional)

Executive Summary: This section turns Section 1's answers into promises a test could fail. The functional surface is tiny: mint one ID, mint a batch, decode an ID for debugging, and never reuse a value. The non-functional side does the work. Uniqueness is absolute, IDs fit in a signed 64-bit integer, they sort by creation time to within about a second, the system handles 1 million IDs/s at peak today and 10 million after growth, NextId has a p99 of 2 ms or less in-region, each region is 99.99 % available, and no ID needs a cross-region conversation. We also list four things we are not promising, and we rank the requirements so the design knows what to give up when two of them collide. In this design, uniqueness always wins, even over availability.

The clarifications are finished, and the interviewer leans back: "Fine. So what are you actually signing up for?" The question tests whether you can write requirements that a test can fail. "IDs should be unique and fast" can't fail anything. "No two calls ever return the same 64-bit value, and NextId returns in 2 ms or less at p99, measured in-region" can, and someone gets paged when it does.

Section 1 left us with one insight to build on. Uniqueness on its own is cheap if you can afford 128 bits. What makes this a design problem is uniqueness plus 64 bits plus rough time order. Every requirement below comes from one of those three constraints or from the scale behind them.

The Functional Contract

The surface is small, and every operation has a reason to exist:

  • Mint one ID (NextId). A caller such as the messaging service asks for an ID and gets back one 64-bit integer. When Leo sends "boarding now" from Lisbon, his message needs one of these before the write can happen.
  • Mint a batch (NextIds(count), count ≤ 1,000). A caller can ask for many IDs in one round trip. Bulk writers need this, and so does the client library that keeps a small buffer of IDs ready. Batching turns out to matter for capacity as well (Section 3 shows why).
  • Decode an ID (Decode(id)). Given an ID, return the timestamp, region, worker and sequence it was built from. This is for humans debugging ("when was this order created, and by which node?"), not for the hot path.
  • Never reuse an ID. Once a value has been issued it is never issued again, whether or not the caller used it, even across restarts, redeploys, or decades.

Things we are leaving out today, each with a reason:

Deferred feature Why it waits Reopened in
Embedded in-process minting Every caller process would need a worker ID, and the worker-ID space is small. That needs its own design Section 11
Shard bits inside the ID Useful only if record placement has to be derivable from the ID, which nobody has asked for yet Section 11
128-bit / UUIDv7 interop Every existing table uses BIGINT keys, so 128 bits is out of scope for now Section 11
Opaque public IDs Only matters once IDs appear in public URLs, and we haven't been told they will Sections 10 and 11

Key Insight: The operation that looks like it is missing is "check whether this ID was already used." It's missing on purpose. At 20 billion IDs a day, a lookup table of issued IDs would itself become a huge database on the hot path. Uniqueness has to come from how an ID is built, not from remembering every ID we have built. Sections 5 and 8 show how.

Quick check before moving on:


Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.