Most rate-limiting advice is about protecting your own servers. Razorpay's flash-sale problem was sneakier: their servers were fine. The thing that would break was a bank they didn't own and couldn't scale.
During a flash sale, a wall of payment requests hits at once. Razorpay can autoscale their own infrastructure — but every payment ultimately flows through an external bank gateway, and you cannot send a bank an autoscaling request. Overrun it and it degrades for everyone, including non-sale traffic.
A rate limiter caps how many requests you let through per unit of time. The usual reason is to stop your own service from being overwhelmed. But there's a second, subtler reason: to protect a downstream dependency you don't control. If your payments flow through a partner that can only handle so much, you must throttle before you flood them — because their failure becomes your failure.
So the limiter isn't selfish protection. It's being a good citizen to a fragile partner, on whom your success depends.
Razorpay put a rate limiter in an Nginx sidecar at the edge, implemented as a fixed-window atomic counter with a TTL: count requests in the current time window, reject past the cap, reset when the window expires. Simple and fast — the atomic counter avoids race conditions under concurrency, and putting it in a sidecar keeps the logic next to the proxy rather than buried in application code.
On top of throttling, they added ML-based gateway routing: rather than sending payments to a fixed or round-robin bank gateway, a model predicts each gateway's current success probability and routes accordingly — steering traffic away from a struggling gateway before it tips over. And critically, they used k6 load testing to find the true bottleneck before a real sale did, so the caps were grounded in measured limits, not guesses.
Worth knowing
The fixed-window counter has a known flaw — bursts straddling the window boundary can briefly allow 2x the limit (which is why sliding-window or token-bucket exist). Razorpay's choice is a reminder that the right algorithm is the simplest one that holds for your traffic shape, not the most sophisticated. Knowing the trade-off lets you defend the choice instead of cargo-culting the fanciest option.
The gap it reveals
Rate limiting is taught as self-defence. The realisation that matters in payments and integrations is that you often limit to protect a downstream you can't autoscale — an external gateway, a legacy system, a partner API — and that the smart move pairs throttling with health-aware routing. Most candidates think 'protect my service'; the senior thinks 'protect the weakest link in the chain.'
In the interview room
Any design with a third-party dependency (payment gateway, SMS provider, partner API) invites: "what happens when that dependency is the bottleneck?" Strong answer: rate-limit at your edge to stay within the partner's capacity, route away from unhealthy instances, and load-test to discover the real ceiling. Naming the fixed-window-vs-token-bucket trade-off is a bonus that shows depth.
The reframe
Your system's reliability is capped by its least scalable dependency, and that dependency is often something you don't own. Scaling your own servers feels like progress, but if you're just delivering more load to a fragile partner faster, you've moved the failure, not removed it. The mature move is to find the real ceiling and shape traffic to respect it.
You can't autoscale a bank. Throttle before the thing that can't grow.
Primary source →
systemdesign.one — How Razorpay Scaled to Handle Flash Sales at 1500 RPS