Concurrency Created: 2026-01-27 Updated: 2026-01-27

Rate Limiting

A pattern for controlling the rate at which operations are performed to prevent resource exhaustion.

Contributors

When to Use

  • When calling external APIs with rate limits
  • When protecting your service from being overwhelmed by requests
  • When controlling resource consumption (database queries, file operations)
  • When implementing throttling for background jobs

When NOT to Use

  • For internal operations where throughput is critical
  • When the cost of rate limiting exceeds the cost of occasional overload
  • In simple applications with predictable low load

Rate Limiting controls the frequency of operations to prevent resource exhaustion and respect external service constraints. Go’s golang.org/x/time/rate package provides a token bucket implementation that’s simple and efficient.

Implementation

package ratelimit

import (
    "context"
    "net/http"
    "time"

    "golang.org/x/time/rate"
)

// APIClient demonstrates rate limiting for external API calls
type APIClient struct {
    limiter *rate.Limiter
}

// NewAPIClient creates a client with specified requests per second and burst capacity
func NewAPIClient(rps int, burst int) *APIClient {
    return &APIClient{
        limiter: rate.NewLimiter(rate.Limit(rps), burst),
    }
}

// Call makes a rate-limited API request
func (c *APIClient) Call(ctx context.Context) error {
    // Wait blocks until a token is available or context is cancelled
    if err := c.limiter.Wait(ctx); err != nil {
        return err
    }
    
    // Perform the actual API call
    return c.doRequest()
}

// TryCall attempts a non-blocking rate-limited call
func (c *APIClient) TryCall() (bool, error) {
    // Allow returns false if no token is available
    if !c.limiter.Allow() {
        return false, nil
    }
    
    return true, c.doRequest()
}

func (c *APIClient) doRequest() error {
    // Actual implementation here
    return nil
}

// RateLimitMiddleware limits HTTP requests per second
func RateLimitMiddleware(rps int, burst int) func(http.Handler) http.Handler {
    limiter := rate.NewLimiter(rate.Limit(rps), burst)
    
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if !limiter.Allow() {
                http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

Usage

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
)

func main() {
    // API client with rate limiting
    client := ratelimit.NewAPIClient(5, 10) // 5 req/s, burst of 10
    ctx := context.Background()
    
    for i := 0; i < 20; i++ {
        if err := client.Call(ctx); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("Request %d completed\n", i+1)
    }
    
    // HTTP server with rate limiting
    mux := http.NewServeMux()
    mux.HandleFunc("/api", handleAPI)
    
    // Limit to 100 requests/second with burst of 200
    handler := ratelimit.RateLimitMiddleware(100, 200)(mux)
    http.ListenAndServe(":8080", handler)
}

func handleAPI(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("API response"))
}

Benefits

  • Resource Protection: Prevents overwhelming external services or internal resources
  • Graceful Degradation: System remains responsive under high load
  • Cost Control: Avoids exceeding paid API quotas or rate limits