System designby Learnastra

System-design interview · Core interviews

Design an API rate limiter

By Anup Rai

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 practice

Useful 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.

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.

  1. 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.

  2. 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.

  3. Recover interrupted internal checks. Gateways assign decision IDs and recover the same recorded outcome after response loss without consuming another slot.

  4. 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

Design diagramAll gateways share one account allowance

The quota transaction completes before admitted work is forwarded.

All gateways share one account allowanceThe quota transaction completes before admitted work is forwarded. client to gateway: Conversion request; gateway to quota: Check and consume; quota to db: Atomic rolling decision; gateway to work: Only after allowConversion requestCheck and consumeAtomic rolling decisionOnly after allowACTORAPI callerSERVICEAuthenticatedgatewaySERVICEQuota transactionAPISTOREUsage and decisionsSERVICEConversion servicesync
Read each connection in order
  1. syncConversion requestAPI caller → Authenticated gateway
  2. syncCheck and consumeAuthenticated gateway → Quota transaction API
  3. syncAtomic rolling decisionQuota transaction API → Usage and decisions
  4. 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.

Request traceTwo callers compete for the final slot

The database serializes the complete transaction, so only one new admission fits.

Two callers compete for the final slotThe database serializes the complete transaction, so only one new admission fits. g1 to db: Lock key; count 2; append third; g2 to db: Wait for same key transaction; db to g1: Committed allow; db to g2: Now count 3; committed denyPARTICIPANTGateway APARTICIPANTGateway BPARTICIPANTQuota database1. Lock key; count 2; append third2. Wait for same key transaction3. Committed allow4. Now count 3; committed denysync
Read each connection in order
  1. syncLock key; count 2; append thirdGateway A → Quota database
  2. syncWait for same key transactionGateway B → Quota database
  3. syncCommitted allowQuota database → Gateway A
  4. 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.

Design diagramRoute each quota key to one replicated decision group

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.

Route each quota key to one replicated decision groupGateways 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. client to gateway: Conversion request; gateway to api: Stable internal decision identity; api to a: Keys assigned to group A; api to b: Keys assigned to group B; gateway to work: Forward only after committed allowConversion requestStable internal decision identityKeys assigned to group AKeys assigned to group BForward only after committedallowACTORConversion callerSERVICEGateways + overloadguardSERVICEQuota API + keyroutingSTOREReplicated quotagroup ASTOREReplicated quotagroup BSERVICEVideo-to-audioservicesync
Read each connection in order
  1. syncConversion requestConversion caller → Gateways + overload guard
  2. syncStable internal decision identityGateways + overload guard → Quota API + key routing
  3. syncKeys assigned to group AQuota API + key routing → Replicated quota group A
  4. syncKeys assigned to group BQuota API + key routing → Replicated quota group B
  5. 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.

Foundation · Question 1

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.

What the answer must demonstrate: States all dimensions of the product contract before storage.

Applied · Question 2

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.

What the answer must demonstrate: Demonstrates the boundary counterexample and distinguishes burst semantics.

Applied · Question 3

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.

What the answer must demonstrate: Makes the eligibility check and effect one atomic operation.

Applied · Question 4

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.

What the answer must demonstrate: Separates internal decision recovery from business idempotency.

Follow-up · Question 5

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.

What the answer must demonstrate: Connects acknowledged-state survival to the chosen availability tradeoff.

Applied · Question 6

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.

What the answer must demonstrate: Uses denial conservatively and never turns cached permission into free quota.

Applied · Question 7

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.

What the answer must demonstrate: Separates arrival rate from in-flight resource use.

Applied · Question 8

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.

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 lesson
Design 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 lesson
Design 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 lesson

Final 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.

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.