System-design interview · Core interviews
Design an API rate limiter
Choose the quota contract first, then enforce one atomic admission decision across gateways without confusing rate limits, retries and business execution.
You will learn to
- Distinguish rolling-window, fixed-window and token-bucket promises.
- Explain atomic check-and-consume with trusted identity and retry recovery.
- State the latency, durability, clock and outage tradeoffs of a shared limiter.
Practice in this chapter
8 interview questions with model answers and follow-ups.
Go to interview practiceUseful foundations: Capacity estimation: throughput, latency, concurrency and storage · Databases, data models, and ACID transactions · Data partitioning and sharding · Caching: cache hits, misses, write policies and invalidation
Workload and timing examples are interview assumptions.
Dotted concept links open the relevant explanation in a new tab.
01Specify exactly what consumes allowance
Design a limiter for a video-to-audio conversion API: account u42 may receive at most three accepted admissions in any rolling 60 seconds across all gateways. An admission is permission to start one conversion attempt. A conversion that later fails still consumes its admission. The limiter is not a billing ledger and does not guarantee that downstream work runs only once.
Use authenticated account identity plus API class as the quota key. The client cannot choose another account, its own policy or a fabricated timestamp. Gateways assign internal decision IDs so a retry of one interrupted limiter call can recover its answer without consuming allowance again. Distinct external attempts receive distinct decision IDs.
Choose strict enforcement for this exercise. If the service cannot establish the current quota state, return unavailable rather than create a fresh local allowance. That sacrifices successful-admission availability during some failures. A best-effort overload guard could deliberately choose a simpler, more available policy, but that would be a different contract.
Ask which identity owns the allowance, what event consumes it, whether bursts are allowed, and whether uncertainty should deny or admit. For this exercise the interviewer accepts a strict rolling admission limit even when that reduces availability.
02Functional requirements
Agree on these supported actions before selecting components.
Decide admission for the correct account. Check the authenticated account and API class before starting a video-to-audio conversion; return an allow, a known quota denial or an unavailable decision.
Apply the stated rolling policy. Allow at most three accepted admissions in any rolling 60 seconds across all gateways. A later conversion failure does not refund that admission.
Recover interrupted internal checks. Gateways assign decision IDs and recover the same recorded outcome after response loss without consuming another slot.
Manage policy and retry guidance. Apply authorized policy changes to existing usage, and return meaningful retry timing for known quota denials without reserving a future slot.
03Non-functional requirements
Use these as illustrative interview assumptions to agree with the interviewer. Numerical targets require measurement; they are not claims about an existing product or a proven implementation. p95 (the 95th percentile) means 95% of measured operations finish within the stated time.
Precision and failure behavior. No extra strict admission is permitted because a gateway, owner or clock is uncertain. Fail closed when safe shared state or trustworthy decision time cannot be established; this intentionally sacrifices admission availability.
Workload. Plan for ten million checks/s, including denied requests. The separate cap-500 storage example sizes a higher configured policy and does not change the three-per-minute running example.
Decision latency. Target p95 of five milliseconds from the trusted gateway’s limiter call to its answer within one region under the admitted planning load. Validate the actual synchronous transaction and hot-key mix; the assumed 50,000 decisions/s owner benchmark is not proof.
Durable accepted usage. An acknowledged allow and its replay result must survive one database-node failure within the region. Failover cannot reset allowance or promote known stale usage as current.
Bounded state and work. Bound decision-retention windows, active keys, retries and downstream concurrency. Live usage cannot be evicted merely to reclaim memory while it still affects the rolling window.
Trusted callers and time. Authenticate gateway callers and policy writers. The client cannot supply another principal, choose its own timestamp or reuse an internal allow as unlimited permission for downstream work.
04Serialize one account’s admission decision
Begin with one application and a SQL database. For each quota key, lock its account-usage row in a transaction. Read the database’s decision time, remove accepted events outside the rolling interval, count those remaining and append a new event only if the count is below three. Save the decision result in that same transaction, then respond after commit.
Suppose u42 was admitted at seconds 0, 10 and 20:
| Decision time | Rolling interval | Prior admissions inside it | Result |
|---|---|---|---|
| Second 50 | (−10, 50] |
0, 10, 20 | All three remain; deny the next request. |
| Second 60 | (0, 60] |
10, 20 | Zero lies on the excluded left boundary. Remove it; with two remaining, a new request can enter. |
The database row lock makes the whole check-and-consume operation indivisible with respect to other requests for u42. If two gateways both compete for the last slot, one completes first; the other sees its accepted event and denies. An atomic counter increment after a separate unlocked check would not provide that result.
Release the transaction before running the conversion. A slow conversion must not hold the quota lock. This is already a working fleet-wide limiter when every gateway uses that database path. Its limits are decision throughput, per-key serialization and the cost of durable state changes.
The quota transaction completes before admitted work is forwarded.
Read each connection in order
- syncConversion requestAPI caller → Authenticated gateway
- syncCheck and consumeAuthenticated gateway → Quota transaction API
- syncAtomic rolling decisionQuota transaction API → Usage and decisions
- syncOnly after allowAuthenticated gateway → Conversion service
05Choose the algorithm that matches the promise
A fixed-minute counter is cheaper, but it answers a different question. Three requests at 12:00:58, :59 and :59.5 followed by three at 12:01:00, :00.2 and :00.4 all pass separate clock-minute buckets. Six admissions occur in 2.4 seconds, violating the chosen rolling rule.
| Algorithm | Stored state | Actual behavior |
|---|---|---|
| Fixed window | Count for one clock interval. | Permits bursts across the boundary. |
| Rolling log | Individual accepted timestamps. | Exact count in the specified interval, under the clock assumption. |
| Weighted adjacent windows | Current and previous bucket counts. | Estimates the rolling count; cannot reconstruct exact times. |
| Token bucket | Available tokens and last refill time. | Allows a chosen burst plus sustained refill rate. |
| Leaky bucket | Bounded queued or scheduled work. | Smooths departures by waiting or dropping excess work. |
For a token bucket of capacity three refilling one token per second, three requests pass immediately from a full bucket; two more can pass two seconds later. That is useful for a burst-tolerant throughput policy, but it cannot stand in for three admissions per rolling minute.
Keep the rolling log for this worked design. Its retained accepted-event count is bounded by the cap under an unchanged policy. A large hourly cap can make that state expensive, which is a reason to renegotiate precision or choose another contract, not silently substitute an approximation.
06Include denied traffic in capacity
Assume one million active identities each generate ten checks/s: ten million limiter decisions/s. An exhausted account may continue sending requests, so a three-per-minute allowance does not bound the request rate reaching the limiter.
| Quantity | Calculation | Meaning |
|---|---|---|
| Decision traffic | 10 million/s × 250 B | About 2.5 GB/s before transport and replication. |
| Owner equivalents | 10 million / measured 50,000 decisions/s | 200 fully busy equivalents; about 334 at 60% planned utilization. |
| Rolling state for cap 500 | 1 million × 500 × 24 B | 12 GB of logical accepted-event state. |
| Three copies of that state | 12 GB × 3 | At least 36 GB before indexes, keys and replay records. |
The 50,000 decisions/s figure is a benchmark input to validate, not a claim about a particular database. Test the actual transaction, replication policy and hot-key distribution against an illustrative five-millisecond decision budget.
Decision replay records have a different bound from accepted events. Attackers can cause many denials, so retain internal decision results for a short documented retry horizon and cap trusted gateway request sizes and identities. Runtime overhead, policy dimensions and duplicate retries may cost more than the packed timestamp bytes.
07Separate a denial from an unavailable limiter
The trusted gateway calls the limiter using this internal operation:
CheckAndConsume(principal, apiClass, decisionId, policyVersion)
| Input | Meaning |
|---|---|
principal |
Authenticated account whose allowance is checked. |
apiClass |
The API category sharing that allowance. |
decisionId |
Identity of this internal check, retained across its retries. |
policyVersion |
The configuration version the gateway expects. |
| Result field | Meaning |
|---|---|
allowed |
Whether this check admits the business request. |
reason |
Why the request was admitted or denied. |
decisionId |
Identifies the recoverable decision. |
| Earliest retry time, for a quota denial | Earliest time another check may succeed; it does not reserve a slot. |
Only an allow result authorizes forwarding the business request.
A known exhausted quota becomes HTTP 429 with suitable Retry-After guidance. An unavailable strict limiter becomes a retryable service error, such as 503, rather than falsely claiming the user exhausted a known quota. Do not publicly cache the 429 response. An internal gateway may retain a conservative private denial hint for that account and API.
At second 50 in the example, the earliest retry time is second 60. If a lost response is recovered at second 59, report approximately one remaining second, rounded conservatively for the public header, not a fresh ten-second delay. Reaching that time permits another check; it does not reserve the next slot.
Policy version identifies configuration, not a new empty usage key. Changing the cap must apply to retained usage. A new longer window requires enough historical data or a conservative transition. A stale gateway must refresh its policy rather than create a separate allowance accidentally.
08Keep rules, usage and recovered answers distinct
| Record | Fields or information | What it answers |
|---|---|---|
Policy |
Account plan, API class, version, cap, window | Which rule applies? |
Usage |
Quota key, event ID, admitted time | Which accepted admissions still consume it? |
Decision |
Quota key, decision ID, payload identity, result, expiry | What answer did this interrupted internal call already commit? |
| Routing partition | Quota-key ownership | Which database group serializes this quota key? |
Index usage by quota key, timestamp and distinct event ID. Two requests may share the same timestamp and still be separate admissions. Remove old individual entries on active keys; whole-key expiry alone does not prune an account that remains busy forever.
Retry recovery checks the decision identity before counting or appending. Reusing that identity with a different payload is rejected. The transaction saves usage and its recoverable answer together. Otherwise a crash can consume allowance without recording what to return on retry.
Never treat live strict usage as disposable cache data. Evicting an active key under memory pressure would reset its allowance. Expire state only after it can no longer affect a policy window or supported retry horizon; shed load or add capacity when live state does not fit.
The database serializes the complete transaction, so only one new admission fits.
Read each connection in order
- syncLock key; count 2; append thirdGateway A → Quota database
- syncWait for same key transactionGateway B → Quota database
- syncCommitted allowQuota database → Gateway A
- syncNow count 3; committed denyQuota database → Gateway B
09Distribute keys while keeping each allowance whole
Partition by a stable hash of account and API class. Each partition is hosted by one replicated database group; its leader processes conflicting transactions in order and acknowledges writes only after the required replicas have durably retained them. Choose a datastore that actually supports this commit and failover contract. Stateless gateways can then scale independently while consulting the same allowance for u42.
A minority partition does not grant strict admissions. Routing changes use the datastore’s validated leadership and state-transfer mechanism rather than starting an empty new counter service. This keeps the interview focused on the admission design while making the required storage guarantee concrete.
A very hot account still has one serialized decision stream. Adding unrelated partitions does not split it safely. Reduce repeated denials by caching the returned deny-until time privately at gateways. When that hint expires, the gateway checks the owner again; it cannot transform an expired denial into a cached allow. A policy increase can invalidate the hint early, otherwise it may conservatively reject for a little longer.
Place a coarse local overload guard before the shared limiter to protect it from attack traffic. This guard can reject extra work but cannot create additional strict allowance. Cache hits, dashboards and approximate remaining-quota displays never reserve permission to execute.
Use three replicas in independent regional failure domains and require a durable majority for the selected one-node-loss target. Measure the five-millisecond p95 gateway-to-limiter budget with that replication path enabled. If it cannot be met, discuss a larger budget or a different enforcement contract rather than weakening acknowledged-state safety silently.
Gateways reject coarse overload locally, then ask the quota API to route account-and-API keys to their replicated group. That group commits both usage and its recoverable answer. Only a committed allow permits forwarding; another group cannot spend the same allowance when the owner is unavailable.
Read each connection in order
- syncConversion requestConversion caller → Gateways + overload guard
- syncStable internal decision identityGateways + overload guard → Quota API + key routing
- syncKeys assigned to group AQuota API + key routing → Replicated quota group A
- syncKeys assigned to group BQuota API + key routing → Replicated quota group B
- syncForward only after committed allowGateways + overload guard → Video-to-audio service
10Recover decisions without granting free work
If the database commits allow for decision r105 but its reply is lost, the gateway retries r105. The saved result returns allow without adding another timestamp. This internal protocol is different from letting a public caller reuse one ID for unlimited conversions. The gateway must also avoid forwarding the same recovered business operation repeatedly without the downstream API’s idempotency protection.
If a database leader fails after acknowledging an admission, the chosen replicated store must preserve that committed event through failover. A replacement reading an older two-event snapshot could grant a fourth slot incorrectly. Plain asynchronous replication is not sufficient evidence for the strict promise; choose weaker enforcement explicitly if that is the available infrastructure.
The worked timestamp arithmetic assumes a trustworthy decision clock. Exact real-time expiry under arbitrary clock faults is outside the basic design; uncertain clock recovery pauses strict admissions rather than expiring records speculatively. Make this assumption explicit instead of treating timestamps as proof of physical time.
Fail closed when the quota group is unreachable or its safe state is uncertain. For a trusted clock moving backward, do not move decision time backward and reopen earlier state. A suspicious forward jump must not expire recent events prematurely. Stop admissions until the service’s time bounds are restored; detailed clock-error accounting is an Advanced follow-up.
Limit retries and time spent waiting. The limiter should reject or fail within a bounded budget instead of accumulating a queue that consumes more resources than the API it protects.
11Protect both the quota and the downstream workers
Measure decision latency, accepted and denied rates, unavailable strict decisions, hot-key traffic, state size and replication health. Compare sampled admissions with a reference rolling-window checker. A low denial rate is not success if the conversion workers are overloaded.
Rate limits control arrivals over time; they do not directly cap concurrent work. If conversions take a minute, even an acceptable sustained admission rate can fill the worker pool. Add a separate bounded work queue and concurrency cap, and decide whether a failed admission-to-execution attempt is charged under the stated rule.
Account rules need verified credentials. IP-only limits can punish unrelated users sharing one public address and can be evaded through address rotation. Use endpoint-specific combinations of account and network controls without allowing attackers to allocate unlimited anonymous keys. Administrative policy changes need authorization and an audit trail.
A policy migration must preserve usage. Raising a cap is straightforward; lowering it may require several old events to expire before another admission fits. Test simultaneous last-slot requests, boundary timestamps, lost replies, failover after acknowledgment and memory pressure. Do not “repair” a strict-state incident by clearing the counters.
12Check the design against the requirements
Use the agreed lists to check the finished design. The tests below still need to establish the targets; a proposed mechanism is not a measured result. FR refers to the numbered functional requirements above; NFR refers to the numbered non-functional requirements.
| Requirement | Design mechanism | Validation and remaining limit |
|---|---|---|
| FR1–2 + NFR1: exact admission | One quota-key transaction prunes, counts and conditionally appends accepted events. | Replay 0/10/20/50/60-second boundaries and race two gateways for the last slot against a reference checker. |
| FR3 + NFR4: safe retry and failover | Usage and replay answer commit together under synchronous replicated storage. | Lose an allow reply and then fail one database node; recovery must not consume again or reopen an old slot. |
| FR4 + NFR5–6: safe policy changes | Authorized versioned policy and retained usage, with bounded private deny hints. | Raise/lower a cap and test a longer-window transition. Expired denial hints require another real check, not an allow. |
| NFR2–3: useful performance | Independent quota-key partitions, coarse overload guards and reduced repeated denial work. | Benchmark p95 at the declared regional load, including synchronous commits and hot keys; renegotiate precision if the budget cannot be met. |
| NFR1,5: failures remain bounded | Conservative clock recovery, fail-closed state uncertainty and separate work-concurrency limits. | Inject clock jumps and owner isolation. Paused strict admissions are an intentional outcome, not a successful fast denial. |
13Rapid revision
Remember: Checking and consuming must happen together; otherwise two gateways can both take the last slot.
| Decision | Why it is needed | Limitation |
|---|---|---|
| Rolling accepted-event log | Enforce three in any 60 seconds. | Store up to the accepted cap per window. |
| Atomic transaction per key | Only one contender can consume the last slot. | Requests sharing one quota key take turns. |
| Stable internal decision ID | Recover a lost answer without double charging. | Does not make downstream execution exactly once. |
| Replicate saved usage and decisions | Preserve usage through failures that replication tolerates. | Pause when disconnected replicas cannot agree safely. |
| Brief gateway cache of a denial | Avoid repeated checks of exhausted quota. | Cached denials cannot grant admission. |
| Use trusted time; recover conservatively | Avoid freeing quota too early. | Pause admission until clock uncertainty is resolved. |
| Separate concurrency control | Protect long-running workers. | A rate cap does not limit simultaneously running jobs. |
In a 90-second close, state the identity, counted event, rolling interval and failure policy. Walk the 0/10/20/50/60-second example, then the two-gateway last-slot race. Finish with the tradeoff: exact shared enforcement costs coordinated state and may reject during uncertainty. If the interviewer instead wants a burst of 100 and a sustained ten per second, choose a token bucket and explain its arithmetic rather than carrying the wrong rolling contract forward.
Practise the interview questions
Say your answer aloud before opening the model answer. Then answer the follow-up and compare the reasoning.
What must be clarified before choosing a limiter algorithm?
Reveal a model answer
Ask whose allowance is shared, which action consumes it, over what interval, whether bursts are allowed and whether an outage should admit or deny. Those answers determine the algorithm and stored state.
Interviewer follow-up
Does a failed conversion refund allowance here?
Reveal the follow-up answer
No. This contract counts accepted admissions, not successful conversions.
What the answer must demonstrate: States all dimensions of the product contract before storage.
Why does a fixed-minute counter violate this rolling policy?
Reveal a model answer
It can admit three just before a clock boundary and three immediately after, all inside one rolling minute.
Interviewer follow-up
Would a token bucket fix the same contract?
Reveal the follow-up answer
Not automatically; token refill and capacity define a burst/rate promise rather than this exact rolling cap.
What the answer must demonstrate: Demonstrates the boundary counterexample and distinguishes burst semantics.
Two gateways see two used slots. How do you avoid admitting both?
Reveal a model answer
Serialize prune, count, compare and insertion inside one quota-key transaction. The second transaction sees the first accepted event.
Interviewer follow-up
Is an atomic increment enough?
Reveal the follow-up answer
No, if its eligibility check occurred separately before another request consumed the slot.
What the answer must demonstrate: Makes the eligibility check and effect one atomic operation.
What happens when an allow reply disappears?
Reveal a model answer
Retry the trusted internal decision ID and recover its saved answer without another usage event.
Interviewer follow-up
Can the public client reuse that identity for free work?
Reveal the follow-up answer
No. The gateway controls admission identities, and business execution has a separate idempotency contract.
What the answer must demonstrate: Separates internal decision recovery from business idempotency.
Why is asynchronous counter replication insufficient for strict failover?
Reveal a model answer
A promoted replica may lack an acknowledged admission and grant extra allowance. The selected datastore must preserve committed usage or stop admission.
Interviewer follow-up
What if availability is more important?
Reveal the follow-up answer
Negotiate an approximate policy with an explicit overshoot budget.
What the answer must demonstrate: Connects acknowledged-state survival to the chosen availability tradeoff.
Why can you cache a deny hint but not an allow?
Reveal a model answer
A conservative denial cannot add extra admissions; reusing an allow would skip the state-changing consume operation.
Interviewer follow-up
Does the retry deadline reserve the next slot?
Reveal the follow-up answer
No. Other requests may consume it before the caller returns.
What the answer must demonstrate: Uses denial conservatively and never turns cached permission into free quota.
Can a rate limit alone protect expensive long-running work?
Reveal a model answer
No. Arrival rate multiplied by execution duration determines in-flight work, so a separate concurrency limit or bounded queue may be needed.
Interviewer follow-up
Should the quota lock be held during execution?
Reveal the follow-up answer
No. Commit and release the admission transaction before running business work.
What the answer must demonstrate: Separates arrival rate from in-flight resource use.
What failure can a forward clock jump cause?
Reveal a model answer
It can make recent admissions appear older than the window and release allowance too early.
Interviewer follow-up
What does this design do during uncertain recovery?
Reveal the follow-up answer
Pause strict admissions until trustworthy time bounds and committed state are established.
What the answer must demonstrate: Explains premature expiry and the explicit trusted-time limit.
Blank-page exercise · 45 minutes
Build the answer yourself
Design a shared limiter for three accepted conversions per rolling minute. Show simultaneous requests, a lost allow reply and an unavailable quota group.
- 0–5 min: agree numbered functional and non-functional requirements for identity, counted event, interval, strict failure behavior, peak checks and decision latency.
- 5–12 min: trace 0/10/20/50/60 and compare algorithms.
- 12–20 min: define atomic transaction, API and replay records.
- 20–30 min: estimate denied traffic and partition independent keys.
- 30–38 min: explain last-slot races, failover and trusted time assumptions.
- 38–45 min: check the final design against the numbered FR/NFR lists with boundary, race, failover and p95-load tests, then discuss explicit precision/availability tradeoffs.
Check that each component and design decision follows from your requirements and workload.
Recall the key ideas
Answer from memory before opening each card. Explain why the choice works and what it costs. Revisit missed cards tomorrow.
Design an API rate limiterTwo gateways see two admissions inside the current three-slot window. What prevents both from admitting another request?Recall first, then reveal
Each runs the whole prune, count, compare, append and decision-record operation in one transaction for that quota key. One commits first; the other sees three and denies.
Check and consume together.
Return to lessonDesign an API rate limiterA denied request receives a retry time. Is a slot reserved for that time?Recall first, then reveal
No. The caller may check again then, but other callers may have consumed the available slots.
Retry is a check, not a ticket.
Return to lessonDesign an API rate limiterWhat happens if the limiter cannot determine how much allowance remains?Recall first, then reveal
Strict enforcement pauses new admissions rather than risk exceeding the shared limit. Durable, coordinated usage records make safe recovery possible.
No state, no new strict allowance.
Return to lessonFinal revision
Summary and interview notes
Gateways share one allowance. Each transaction checks and consumes a slot together; saved usage and decisions prevent retries or supported failover from granting extra slots.
Remember these points
- Choose the time contract before the algorithm.
- Serialize the complete check-and-consume.
- Recover lost answers with internal identities.
- Preserve committed usage or stop strict admissions.
Interview tips
- Use the boundary burst to distinguish algorithms.
- Count denied traffic when estimating capacity.
Important qualifications
- The limiter is not a billing ledger or an exactly-once executor.
- Clock-fault guarantees and multi-region credit protocols require deeper treatment.
Continue after the core interview
Explore the advanced version
The advanced lesson keeps the full detailed design. Use these sections when you want to examine the stronger requirements and failure cases.
- Clock bounds and conservative physical-time expiry
Derive a timed guarantee under measured clock uncertainty rather than the basic trusted-clock assumption.
- Multiple quota dimensions and ownership transfer
Analyze cross-owner reservations and stale ownership in detail.
- Ordered policy rollout
Define when a new rule is enforced by every serving partition.
- Global and regional credit allocation
Explore burst-oriented reserved budgets without claiming independent regions share exact rolling state.
Technical references
- Redis: atomic script executionExplains server-side atomic execution and why scripts must remain short; not a promise of transactional rollback.
- RFC 6585: HTTP 429Defines Too Many Requests, optional Retry-After, and response caching restrictions.
- Redis WAIT consistency limitationsReplica acknowledgment waiting improves safety but does not establish strong consistency or guaranteed lossless failover.
- Redis: rate-limiting algorithm comparisonStandard names and mechanisms for fixed windows, sliding-window logs, sliding-window counters, token buckets and leaky buckets; an approximation does not establish our strict rolling guarantee.
Practice marks stay in this browser.