Simple Rate Limiter in Go Using Maps & Timestamps

What Is Rate Limiting and Why Do We Need It?

Imagine you own a small ice cream shop. If too many people rush in at once, you can't serve everyone quickly, and some customers might leave unhappy. Rate limiting works the same way for computer programs—it controls how many requests a server can handle in a certain time to prevent overload.

How Does a Simple Rate Limiter Work?

A basic rate limiter tracks how many times a user (or IP address) makes a request. If they exceed a set limit (like 10 requests per minute), the server blocks further requests until the time window resets. In Go, we can build this using maps (to store request counts) and timestamps (to track time windows).

Building a Rate Limiter in Go: Step by Step

Step 1: Define the Rate Limiter Structure

First, we need a way to store the number of requests per user and their last request time. We'll use a map where the key is the user's identifier (like an IP address) and the value is a struct containing their request count and timestamp.

type RequestRecord struct {
    Count     int
    Timestamp time.Time
}

var rateLimiter = make(map[string]RequestRecord)

Step 2: Implement the Rate Check Logic

For each request, we check if the user has exceeded the limit. If not, we allow the request and update their count. If yes, we block it.

func AllowRequest(userIP string, limit int, window time.Duration) bool {
    record, exists := rateLimiter[userIP]
    now := time.Now()

    // If record doesn't exist or window has expired, reset
    if !exists || now.Sub(record.Timestamp) > window {
        record = RequestRecord{Count: 1, Timestamp: now}
        rateLimiter[userIP] = record
        return true
    }

    // If under limit, increment count
    if record.Count < limit {
        record.Count++
        rateLimiter[userIP] = record
        return true
    }

    // Otherwise, deny request
    return false
}

Step 3: Testing the Rate Limiter

Let’s simulate multiple requests from the same IP to see if the limiter works.

func main() {
    userIP := "192.168.1.1"
    limit := 5
    window := time.Minute

    for i := 0; i < 10; i++ {
        allowed := AllowRequest(userIP, limit, window)
        fmt.Printf("Request %d: Allowed=%v\n", i+1, allowed)
        time.Sleep(10 * time.Second) // Simulate delay between requests
    }
}

Real-World Use Case: API Rate Limiting

Suppose you run a weather API. Without rate limiting, a single user could flood your server with thousands of requests, slowing it down for others. By adding this limiter, you ensure fair usage.

Limitations and Improvements

This simple limiter works for small-scale apps, but it has flaws:

  • Memory usage grows as more users make requests.
  • It doesn’t handle distributed systems (where multiple servers share rate limits).

For production, consider using Redis or a dedicated library like golang.org/x/time/rate.

Conclusion

Building a basic rate limiter in Go is straightforward with maps and timestamps. While this version isn’t perfect for large-scale apps, it’s a great starting point to understand how rate limiting works.

Read more