Free preview

Requirements, Throttling Types, and Placement

In one line: "rate limiting" is usually treated as one behaviour. It is three — hard, soft, and elastic — and picking the wrong one either drops good traffic or fails to protect you.

Requirements

Functional:

  • Limit the number of requests a client can send within a time window.
  • Ensure request limits per window are configurable.
  • Notify the client (via error or notification) when a threshold is crossed.

Non-functional:

RequirementDetail
AvailabilityThe rate limiter must protect the system and be highly available
Low latencyChecks must occur with minimal latency to avoid impacting user experience
ScalabilitySupport an increasing number of requests and clients

The availability requirement is unusually sharp here — the limiter is on every request

Most components can be unavailable and degrade something. A rate limiter sits in front of everything, so its availability is a ceiling on the whole service's availability.

That produces a genuinely hard question, and interviewers ask it: if the rate limiter fails, do you accept requests or reject them?

  • Fail open (accept): the service stays up, but is now unprotected — and the limiter is most likely to fail precisely when traffic is highest, which is exactly when protection matters.
  • Fail closed (reject): protection holds, but a limiter outage becomes a total outage for a component whose only job was to protect against outages.

The usual answer is fail open, with the reasoning stated: rate limiting is a defence-in-depth measure, and the service has other protections — load shedding, autoscaling, connection limits. Turning a limiter failure into a full outage inverts the component's purpose.

That said, fail closed for anything security-critical — a login endpoint with brute-force protection should not open its doors because a counter store is unreachable. The right answer is often per-endpoint, not global, and saying so is the senior version.

Low latency is why this design caches so aggressively

The limiter is on the critical path of every single request. If the check costs 20 ms, you have added 20 ms to every request in the system — including all the ones that were well within their limit.

That is why Lesson 5 puts throttle rules in an in-memory cache rather than reading the rule database per request, and why Lesson 7 goes further and moves the counter update off the critical path entirely.

The general principle worth naming: anything on every request must be measured in microseconds, not milliseconds. A component that adds latency to 100% of traffic to protect against 0.1% of it has to be very cheap to be worth it.

Types of throttling

TypeBehaviourExample
Hard throttlingEnforces a strict limit. Any request exceeding the threshold is discardedLimit 500 → request 501 is rejected
Soft throttlingAllows requests to exceed the limit by a specific percentageA limit of 500 with a 5% buffer allows 525 requests
Elastic or dynamic throttlingAllows requests to exceed the limit if the system has free resources, without a fixed upper capOver the limit but the cluster is idle → serve it

Elastic throttling is the most useful and the most dangerous

It is appealing: why reject a request when there is capacity sitting idle? Spare capacity that goes unused is waste.

The danger is that it trains your callers on capacity you did not promise. A client that consistently gets 800 requests through against a 500 limit will build against 800 — and then be broken the day the cluster is genuinely busy and the limit actually bites. You have converted a clear contract into an unpredictable one.

It also couples the limit to system load, which means behaviour changes under exactly the conditions where you most want predictability. Debugging "why did this work yesterday" gets much harder.

If you use elastic throttling, the mitigation is to make the excess visible — return headers showing the nominal limit and that the request was served on spare capacity — so clients cannot silently come to depend on it.

Where to place it

PlacementDetail
Client sideEasy to implement, but vulnerable to tampering and difficult to configure securely
Server sideRuns directly on the server, checking requests as they arrive
MiddlewareActs as an intermediary, throttling requests before they reach API servers

Note: API endpoints are ideal vantage points for rate limiting because all traffic passes through them.

Placement is a subjective decision based on the organization's technology stack, engineering resources, and goals.

Client-side rate limiting is not a security control at all

"Vulnerable to tampering" understates it. Anything running on the client is entirely under the attacker's control — they can patch it, disable it, or skip your client and call the API directly with curl.

So client-side limiting protects against exactly one thing: accidental over-calling by a well-behaved client. That is genuinely useful — it prevents a UI bug from hammering your API, and it saves the user's bandwidth and battery. But it must be treated as an optimization, never an enforcement point.

The rule: client-side limiting reduces load; server-side limiting provides the guarantee. Do both, and never rely on the first for anything that matters.

Middleware is the usual answer, and the reason is reuse

Server-side works, but it puts limiting logic in every service — which means every team implements it, every team gets it slightly wrong, and rules live in N places.

Middleware — an API gateway, a sidecar, a dedicated service — gives you one implementation and one place to configure rules, and every service behind it is protected whether or not its authors thought about it. It also rejects over-limit traffic before it consumes application resources, which is the point of limiting in the first place.

The cost is an extra hop, and the fact that the middleware becomes a shared dependency whose failure affects everything — which is the fail-open question again, now with a bigger blast radius.

Building blocks used

Building blockUsed for
DatabasesStore service rules and user metadata
CachesProvide fast access to rules and user data
QueuesBuffer incoming requests allowed by the rate limiter

Key takeaway

Three throttling behaviours: hard (strict), soft (a fixed percentage of headroom), elastic (use spare capacity, no cap). Placement is client (an optimization only), server (works, duplicated everywhere), or middleware (one implementation, one config, an extra hop). And the limiter's own availability caps the service's, which forces a fail-open or fail-closed decision.

Interview signal by level

LevelWhat a strong answer sounds like
L4"Put a rate limiter in front of the API and reject requests over the limit."
L5Distinguishes the modes and placements: "middleware rather than per-service, so rules live in one place, and I'd use soft throttling with a small buffer rather than a hard cut-off."
Staff+Answers the failure question with nuance: "if the limiter fails I'd fail open by default — it's defence in depth, and turning its outage into a total outage inverts its purpose — but fail closed on security-critical endpoints like login. That's a per-endpoint decision, not a global one. And client-side limiting is an optimization only; it's fully under the attacker's control."

Next: making it work across many nodes and data centers.

Enjoying the preview?

Create a free account to unlock the rest of this course, the in-browser judge, and live AI mock interviews.

Sign up free to continue