Hirestack
Hirestack
Chapter 19

Problem 15: Design a Rate Limiter (LLD)

📖 2 min read · 3 sections · May 2026
Asked at: Cloudflare Stripe Microsoft

The setup

"Implement a rate limiter that can be embedded in an API server. Configurable algorithm (token bucket, sliding window, etc.). Support per-user, per-API limits. Thread-safe."

The HLD version was about distributed state. The LLD version focuses on the algorithm classes and their integration.

The Strategy pattern

interface RateLimiterAlgorithm {
  boolean isAllowed(String key);
}

class TokenBucketRateLimiter implements RateLimiterAlgorithm {
  private final int bucketSize;
  private final double refillRatePerSecond;
  private final Map buckets = new ConcurrentHashMap<>();
  
  public synchronized boolean isAllowed(String key) {
    Bucket bucket = buckets.computeIfAbsent(key, k -> new Bucket(bucketSize));
    bucket.refill(refillRatePerSecond);
    if (bucket.tokens >= 1) {
      bucket.tokens -= 1;
      return true;
    }
    return false;
  }
  
  private static class Bucket {
    double tokens;
    Instant lastRefill;
    void refill(double ratePerSecond) {
      Instant now = Instant.now();
      double elapsedSeconds = Duration.between(lastRefill, now).toMillis() / 1000.0;
      tokens = Math.min(bucketSize, tokens + elapsedSeconds * ratePerSecond);
      lastRefill = now;
    }
  }
}

class SlidingWindowRateLimiter implements RateLimiterAlgorithm {
  private final int maxRequests;
  private final long windowMillis;
  private final Map> requestLogs = new ConcurrentHashMap<>();
  
  public synchronized boolean isAllowed(String key) {
    Deque log = requestLogs.computeIfAbsent(key, k -> new ArrayDeque<>());
    Instant now = Instant.now();
    Instant cutoff = now.minusMillis(windowMillis);
    while (!log.isEmpty() && log.peekFirst().isBefore(cutoff)) {
      log.pollFirst();
    }
    if (log.size() < maxRequests) {
      log.offerLast(now);
      return true;
    }
    return false;
  }
}

class RateLimiter {
  private final RateLimiterAlgorithm algorithm;
  
  public RateLimiter(RateLimiterAlgorithm algorithm) {
    this.algorithm = algorithm;
  }
  
  public boolean isAllowed(String key) {
    return algorithm.isAllowed(key);
  }
}

Takeaway

The LLD version of the rate limiter exposes the algorithmic options as a clean strategy hierarchy. Combined with the HLD version (where state lives across nodes), you have the full picture. The LLD is the in-process algorithm; the HLD is the distributed state — both matter.