When Your API Cries "SOS": Introduction to Rate Limiting and Throttling
\n\nImagine: you've written a perfect Kotlin application. It's fast, elegant, uses coroutines and Flow. You deploy it to production, and then — bam! — one "kind" user (or, worse, an attacker) starts hammering your endpoint with thousands of requests per second. The server crashes, the database cries, and other users get 503 Service Unavailable.
Sound familiar? This is exactly why rate limiting and throttling mechanics exist. In this article, we won't just talk about theory, but will break down how to implement these patterns in Kotlin with practical examples.
\n\nWe'll write code that will actually protect your backend, whether it's a Ktor application, Spring Boot, or pure coroutines. Let's go!
\n\nRate Limiting vs Throttling: What's the Difference (and Why Does It Matter)?
\n\nLet's start with the basics to avoid confusion with terminology. Although these concepts are often used as synonyms, there is a nuance between them.
\n\n- \n
- Rate Limiting — is a hard limit. "You can only make 100 requests per minute. After the 101st — you'll get
429 Too Many Requestsand a ban until the next minute." This is about a limit. \n - Throttling — is a softer regulation. "You cannot send requests faster than once every 200 ms. If you send faster — we'll queue them or simply ignore them." This is about speed. \n
In the Kotlin world, we most often implement rate limiting at the API level (middleware) and throttling at the level of specific operations (for example, to avoid spamming an external server with requests).
\n\nAlgorithms Every Kotlin Developer Should Know
\n\nBefore writing code, let's choose a tool. There are three classic algorithms that fit perfectly into Kotlin architecture.
\n\n1. Token Bucket — The Most Popular
\nImagine a bucket into which N new tokens drip every second. The bucket has a maximum capacity (burst). To process a request, you need to take a token. If there are no tokens — the request is rejected.
\nPros: Allows small bursts of load, simple to implement.
\n\n2. Leaky Bucket — Smoothing Peaks
\nA bucket with a hole. Requests go into the bucket, and the "hole" releases them at a constant rate. If the bucket overflows — requests are discarded.
\nPros: Perfectly evens out the load, prevents peaks.
\n\n3. Sliding Window — Precision Without Jerks
\nInstead of rigid boundaries (like in "1 minute"), we look at a sliding time window. More complex to implement, but more accurately reflects the real load.
\n\nFor Kotlin projects, Token Bucket is the sweet spot between simplicity and efficiency. That's what we'll implement.
\n\nWriting Code: Rate Limiter in Kotlin from Scratch (Without Libraries)
\n\nLet's create a simple but working Rate Limiter using coroutines and Mutex. This will be a thread-safe version of Token Bucket.
import kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlin.time.Duration\nimport kotlin.time.TimeSource\n\nclass TokenBucketRateLimiter(\n private val maxTokens: Int,\n private val refillRate: Long, // tokens per second\n private val timeSource: TimeSource = TimeSource.Monotonic\n) {\n private var tokens: Long = maxTokens.toLong()\n private var lastRefillTimestamp: Long = timeSource.markNow().elapsedNow().inWholeMilliseconds\n private val mutex = Mutex()\n\n suspend fun tryAcquire(): Boolean {\n mutex.withLock {\n refillTokens()\n return if (tokens > 0) {\n tokens--\n true\n } else {\n