AniUI Academy

Rate limit to N calls per window

hardFunctions & Closures+350 XPPractice

Client-side rate limiting keeps you inside an API's quota and stops a runaway effect from firing a hundred requests. The subtlety is that the window rolls: the limit is on any windowMs stretch ending now, not on fixed buckets. A naive counter reset every windowMs lets through twice the limit across a bucket boundary.

Implement rateLimit(fn, limit, windowMs). It returns a wrapper. On each call, count the allowed invocations that happened within the last windowMs milliseconds; if that count is below limit, invoke fn with the wrapper's arguments and its this, record the time of the invocation, and return fn's result. Otherwise drop the call — fn is not invoked — and return undefined. Invocations older than windowMs no longer count towards the limit, so a call becomes possible again as soon as the oldest recorded invocation ages out. A limit of 0 means fn is never invoked.

Arguments

fnFunction
The function to rate-limit.
limitnumber
Maximum number of allowed invocations in any windowMs stretch.
windowMsnumber
Width of the rolling window, in milliseconds.

Returns

Function — A wrapper that invokes fn up to limit times per rolling windowMs and drops the rest.

Examples

let calls = 0;
const run = rateLimit(() => ++calls, 3, 1000);

run(); run(); run(); // 1, 2, 3
run();               // undefined — over the limit this window
// 1s later, oldest invocation ages out — next call fires again

The window rolls: a slot opens up as soon as the oldest recorded invocation turns windowMs old.

const never = rateLimit(() => "x", 0, 1000);
never(); never(); // both undefined, fn never invoked

What it has to do

  • Allow up to limit invocations in any windowMs stretch.
  • Drop calls beyond the limit without invoking fn, returning undefined.
  • Use a rolling window: an invocation stops counting once it is older than windowMs.
  • Forward arguments and preserve this for allowed calls, returning fn's result.
  • Never invoke fn when limit is 0.

Notes

  • A rolling window and a fixed bucket are not the same thing. A counter that resets every windowMs lets 2 * limit calls through across a bucket boundary.
  • Keep an array of timestamps of allowed invocations. On each call, filter out the ones older than windowMs first, then compare the remaining length against limit.

Stuck? The javascript course covers everything this challenge needs.

Try it yourself
Loading playground...
5 tests · +350 XP

Discussion

Sign in to post
Sign in to join the conversation.Sign in

No comments yet. Be the first to say something.