WebTech1223411 logo WebTech1223411Web tech, read closely
Back-End

How Rate Limiting Protects an API Without Punishing Real Users

Every public API eventually meets a client that sends far more requests than any reasonable use case requires, whether that is a misconfigured script stuck in a retry loop, a scraper harvesting data as fast as the connection allows, or a...

A diagram of API requests passing through a rate limiting gate

Every public API eventually meets a client that sends far more requests than any reasonable use case requires, whether that is a misconfigured script stuck in a retry loop, a scraper harvesting data as fast as the connection allows, or a genuine attack trying to find a weak spot through brute force. Rate limiting is the standard defence, and it is also one of the easiest features to implement badly, in a way that stops the abusive traffic while also making life miserable for the well behaved client who just happens to need a few more requests than average during a busy moment.

What rate limiting is actually protecting

A backend service has finite capacity: a certain number of database connections, a certain amount of processing time, a certain amount of memory for caching. One client sending requests far faster than any human or reasonable integration would, intentionally or not, can consume a disproportionate share of that finite capacity, starving every other client of the service they are also paying for or relying on. Rate limiting exists to guarantee a fair, predictable share of capacity to every client, not simply to be difficult. Keeping that goal in mind changes how a limit should be designed, since the aim is fairness and stability, not punishment.

Fixed windows create an unfair edge effect

The simplest rate limiting approach resets an allowance at a fixed point, such as allowing one hundred requests per minute, counted from the top of each clock minute. This sounds reasonable until a client sends fifty requests in the final second of one window and another fifty in the first second of the next window, which is only one hundred requests across roughly two seconds of real time but counts as compliant under two separate windows. A sliding window, which looks back over the most recent period of time rather than resetting at a fixed clock boundary, avoids this edge effect and gives a far more honest, consistent picture of actual request rate regardless of when a client happens to send its burst.

A burst allowance matters more than most initial designs include

Real client behaviour is rarely smooth and constant. A mobile app that syncs several screens worth of data the moment it opens, or a dashboard that loads six widgets at once, both produce a short, legitimate burst of requests followed by long stretches of near silence. A rate limit with zero tolerance for bursts punishes exactly this normal pattern, forcing developers to add artificial delays between requests that make their own legitimate application feel slower than it needs to be. A token bucket approach, which allows a reasonable burst up to a cap while still enforcing a steady average rate over time, handles this far better than a rigid per-second cap, and connects to the broader design goal covered in how to design a REST API other developers enjoy using, where predictable, humane behaviour under normal use is treated as a design requirement, not an afterthought.

The response needs to tell clients exactly what happened

When a request is rejected for exceeding a limit, the response should clearly state that this is a rate limit, not a generic error, and should include how many requests remain in the current window and when the limit resets. Without this information, a developer integrating with the API has no way to build sensible retry logic and will often resort to guessing, which tends to produce either overly aggressive retries that make the problem worse or overly cautious ones that make their own application feel broken. A few standard response headers carrying this information turn a frustrating black box into something a well behaved client can work with gracefully.

Different endpoints deserve different limits

A single global rate limit applied uniformly across an entire API treats a cheap, simple read request the same as an expensive search or export operation that consumes far more backend resource per call. This often means the expensive endpoint is the one actually at risk of overloading the service while being allowed just as many calls as the cheap one, and the cheap endpoint ends up rate limited unnecessarily for a workload it could easily handle. Setting limits per endpoint, based on actual resource cost rather than a single blanket number, matches the protection to where capacity is genuinely scarce.

Scaling capacity and limiting requests are solving different problems

It is worth being clear that rate limiting is not a substitute for having enough server capacity, and the two are often confused. Scaling a backend to handle more simultaneous load, the kind of problem covered in how online game platforms scale their servers on busy nights, solves a different issue than rate limiting does. Rate limiting protects fairness and stability at a given capacity level. Scaling raises that capacity level in the first place. A service that only scales without ever rate limiting is still vulnerable to one misbehaving client consuming everything, and a service that only rate limits without ever scaling will simply hit its ceiling sooner as legitimate traffic grows.

A short checklist for a fairer rate limit

  1. Use a sliding window instead of a fixed clock-reset window to avoid the edge-burst problem.
  2. Allow a reasonable short burst rather than enforcing a perfectly flat rate.
  3. Return clear headers showing remaining requests and reset time, not just a bare rejection.
  4. Set limits per endpoint based on actual resource cost, not one number for the whole API.
  5. Remember that rate limiting and capacity scaling solve different problems and need both addressed.

Done well, a rate limit is something well behaved clients barely notice and abusive traffic cannot get past. Done poorly, it becomes the thing every integrator complains about regardless of how carefully they built their own client. More backend design thinking in our Back-End section.

KO
Kofi Oosterhuis

Kofi spent years keeping APIs and game servers alive through traffic spikes and the occasional bad deploy. He covers back-end design, scaling and networking, with a preference for boring systems that do not page anyone at night.

More posts by Kofi

More in Back-End

01doorganico 084goad 3hot8 4465456 after5internprogram ahandfulofblessings ahmadpc ahomcybersecurity allodialresources annodex antiquesandperiodcollectibles aotc asianworldkashmir beedlectrics bloggertemplatefree bluefeatheragency bodyweightsolution campaign08blog carfixadvisor catstreetcollective cbstvsports cnfanspurchasing committeeof500yearsofdignityandresistance covertopsdjs cs-kaiyuangame cuestacomunicaciontotal cxoutlet dancebetgame deskgearhub destinyvillastay detectmagazine di33 diamondsteelbuildings donmo dyanadropship emarsysfinance emimattl escapechan eskitarih evautoplay everythingweddings-orlando farmzone feedviewerapp foxstudiosscratch gamevui123 gleasongrade greetingsday hacklabasu haskell iw-kaiyun jagoanproperti jilidskripsi joltbusinessfinance juegodepokergratis juegospucca kaeru-festival latestmehndidesigns lestanzediottaviano litemagazines localcommunityhero lustrsazan mariashebets menswheels newwebstechs1 nikkit ootheday oxigenvpn plurfitness prefetial probudgetcarpetcleaning psychicmrsmartin pyaten qvantor rejoignezlogis rexzbot riselayouts ruchin rwxqsgd5 sakaltime shibiaowang shop-bot skrtips strcoding tetapatrium topselller uneplanteartificielle vipertechcr wo-kygaming yamaha-mekar yeknava yoybuysheets zenunwa yamaha-mekar jilidskripsi ankertoto ankertoto ankertoto ankertoto