Rate Limiting and Throttling in C#: How to Protect Your API from Overloads and Keep Users Happy
\n\nImagine: you've launched a cool API. The server is flying, the database responds instantly. But suddenly — bam! — one careless client (or, even worse, a DDoS attack) starts hammering your endpoint with thousands of requests per second. The server crashes, the database chokes, and real users see a 503 error. Sound familiar?
\n\nThe solution to this problem is rate limiting and throttling. In C#, we have all the tools to implement these mechanisms elegantly and efficiently. In this article, we won't just go through the theory — we'll write real code, break down popular algorithms, and figure out what to choose for a specific task. Buckle up, it's going to be intense!
\n\nRate Limiting vs Throttling: What's the Difference?
\n\nAlthough these terms are often confused, there is a clear line between them:
\n- \n
- Rate Limiting is a "hard" block. If a user exceeds the limit (e.g., 100 requests per minute), the server returns HTTP 429 (Too Many Requests). Requests simply don't reach the business logic. \n
- Throttling is a "soft" slowdown. The server accepts the request but processes it with a delay or lowers its priority. The user doesn't get an error, but their speed drops. \n
In C#, we can implement both approaches, often combining them for different scenarios.
\n\nRate Limiting Algorithms: From Simple to Complex
\n\nBefore writing code, let's understand the math. There are four "golden" algorithms that form the basis of 90% of solutions.
\n\n1. Fixed Window
\nThe simplest way. We divide time into fixed intervals (e.g., 1 minute) and count requests in each window. Once the counter reaches the limit — we block.
\nPros: Simplicity of implementation. Cons: The "edge effect" problem (burst at the window boundary).
\n\npublic class FixedWindowRateLimiter\n{\n private readonly int _limit;\n private readonly TimeSpan _windowSize;\n private int _counter;\n private DateTime _windowStart;\n\n public FixedWindowRateLimiter(int limit, TimeSpan windowSize)\n {\n _limit = limit;\n _windowSize = windowSize;\n _windowStart = DateTime.UtcNow;\n }\n\n public bool AllowRequest()\n {\n var now = DateTime.UtcNow;\n if (now - _windowStart >= _windowSize)\n {\n // Reset window\n _counter = 0;\n _windowStart = now;\n }\n\n if (_counter >= _limit)\n return false; // Too Many Requests\n\n Interlocked.Increment(ref _counter);\n return true;\n }\n}\n\n2. Sliding Window
\nAn evolution of Fixed Window. Instead of rigid boundaries, we account for requests in a sliding time segment. It is usually implemented by logging timestamps (e.g., in a circular buffer).
\nPros: Smooths out bursts. Cons: Slightly more complex to implement, requires memory to store timestamps.
\n\npublic class SlidingWindowRateLimiter\n{\n private readonly int _limit;\n private readonly TimeSpan _windowSize;\n private readonly Queue<DateTime> _timestamps = new();\n\n public SlidingWindowRateLimiter(int limit, TimeSpan windowSize)\n {\n _limit = limit;\n _windowSize = windowSize;\n }\n\n public bool AllowRequest()\n {\n var now = DateTime.UtcNow;\n lock (_timestamps)\n {\n // Remove outdated timestamps\n while (_timestamps.Count > 0 && now - _timestamps.Peek() > _windowSize)\n _timestamps.Dequeue();\n\n if (_timestamps.Count >= _limit)\n return false;\n\n _timestamps.Enqueue(now);\n return true