Your API Under Attack: Why Rate Limiting and Throttling Are a Must-Have for Swift Developers
\n\nImagine: you launched a cool Swift app, users are thrilled, servers are working perfectly. But suddenly — a collapse. Someone runs a script that hammers your API with thousands of requests per second. Or just one active user spams the "Refresh" button faster than you can process the data. The result — slowdowns, server crashes, and negative reviews.
\n\nSound familiar? Then this article is for you. We'll break down rate limiting and throttling in Swift — techniques that will save your backend and make your app stable. You'll learn how to implement request limiting in Swift on the client side using modern approaches with async/await, and which patterns actually work.
\n\nRate Limiting vs Throttling: What's the Difference?
\n\nBefore writing code, let's clarify the terms. Many people confuse these concepts, although they solve different problems.
\n\nRate Limiting
\nThis is a hard limit: "No more than N requests per period." If the limit is exceeded — the request is rejected. The server usually returns 429 Too Many Requests. Example: you allow no more than 100 requests per minute from one IP.
Throttling
\nThis is a softer mechanism: requests are not blocked but queued and executed at a specified frequency. Imagine your API can only handle 5 requests per second. Throttling will queue them and send them at the required speed without overloading the server.
\n\nIn Swift, both approaches are implemented differently. Let's look at the best practices.
\n\nPractical Implementations of Rate Limiting in Swift
\n\n1. Simple Timer-Based Throttling (GCD)
\n\nThe simplest way to limit requests in Swift is to use DispatchQueue and a timer. Here's an example that ensures requests are sent no more than once per second:
\n\nimport Foundation\n\nfinal class RequestThrottler {\n private let queue = DispatchQueue(label: "com.app.throttle")\n private var lastRequestTime: Date = .distantPast\n private let minimumInterval: TimeInterval\n \n init(interval: TimeInterval = 1.0) {\n self.minimumInterval = interval\n }\n \n func performThrottledRequest(block: @escaping () -> Void) {\n queue.sync {\n let now = Date()\n if now.timeIntervalSince(lastRequestTime) >= minimumInterval {\n lastRequestTime = now\n DispatchQueue.main.async {\n block()\n }\n } else {\n print("Request rejected: too frequent")\n }\n }\n }\n}\n\n// Usage\nlet throttler = RequestThrottler(interval: 2.0)\nthrottler.performThrottledRequest {\n // Your API request\n print("Request executed")\n}\n\nThis approach is simple but not ideal: it simply discards "excess" requests. For many scenarios this is enough, but if you need to preserve order and guarantee execution — read on.
\n\n2. Rate Limiter with Queue (async/await)
\n\nWith the advent of Swift async/await, we got an elegant way to implement a swift rate limiter with queue support. Here's how it looks:
\n\nimport Foundation\n\nactor RateLimiter {\n private let maxRequests: Int\n private let perSecond: Double\n private var requestTimestamps: [Date] = []\n private var pendingTasks: [CheckedContinuation] = []\n private var isProcessing = false\n \n init(maxRequests: Int, perSecond: Double) {\n self.maxRequests = maxRequests\n self.perSecond = perSecond\n }\n \n func waitForSlot() async throws {\n try await withCheckedThrowingContinuation { continuation in\n pendingTasks.append(continuation)\n if