Skip to main content

Design a Rate Limiter system

Problem Statement

Design a Rate Limiter system that controls how many requests a user or service can make in a given time window. The system should prevent abuse, protect backend services, and ensure fair usage across all users.


Functional Requirements

  • The system should limit the number of requests per user or IP
  • It should support Fixed Window algorithm
  • It should support Sliding Window algorithm
  • Each request should be either allowed or rejected
  • The limiter should work in-memory (can be extended later to distributed systems)

Objects Required

  • RateLimiter
  • FixedWindowRateLimiter
  • SlidingWindowRateLimiter
  • RequestBucket
  • UserKey (identifier)

RateLimiter Interface


public interface RateLimiter {
    boolean allowRequest(String userId);
}

The RateLimiter interface defines a simple contract for all rate limiting strategies. Any implementation must decide whether a request should be allowed or blocked.


FixedWindowRateLimiter Class


import java.util.concurrent.ConcurrentHashMap;

public class FixedWindowRateLimiter implements RateLimiter {

    private final int maxRequests;
    private final long windowSizeInMillis;

    private static class WindowData {
        int count;
        long windowStart;
    }

    private ConcurrentHashMap store = new ConcurrentHashMap<>();

    public FixedWindowRateLimiter(int maxRequests, long windowSizeInMillis) {
        this.maxRequests = maxRequests;
        this.windowSizeInMillis = windowSizeInMillis;
    }

    @Override
    public synchronized boolean allowRequest(String userId) {

        long currentTime = System.currentTimeMillis();

        WindowData data = store.getOrDefault(userId, new WindowData());

        if (currentTime - data.windowStart > windowSizeInMillis) {
            data.windowStart = currentTime;
            data.count = 0;
        }

        if (data.count < maxRequests) {
            data.count++;
            store.put(userId, data);
            return true;
        }

        return false;
    }
}

The Fixed Window strategy divides time into fixed intervals (like 1 minute windows).

The constructor sets the maximum allowed requests and the time window size.

The allowRequest() method checks whether the current request falls into the same window. If the window has expired, it resets the counter.

If the request count is within limit, it is allowed; otherwise, it is rejected.


SlidingWindowRateLimiter Class


import java.util.*;

public class SlidingWindowRateLimiter implements RateLimiter {

    private final int maxRequests;
    private final long windowSizeInMillis;

    private static class RequestLog {
        Queue timestamps = new LinkedList<>();
    }

    private Map store = new HashMap<>();

    public SlidingWindowRateLimiter(int maxRequests, long windowSizeInMillis) {
        this.maxRequests = maxRequests;
        this.windowSizeInMillis = windowSizeInMillis;
    }

    @Override
    public synchronized boolean allowRequest(String userId) {

        long now = System.currentTimeMillis();

        RequestLog log = store.getOrDefault(userId, new RequestLog());

        while (!log.timestamps.isEmpty()
                && now - log.timestamps.peek() > windowSizeInMillis) {
            log.timestamps.poll();
        }

        if (log.timestamps.size() < maxRequests) {
            log.timestamps.add(now);
            store.put(userId, log);
            return true;
        }

        return false;
    }
}

The Sliding Window strategy is more accurate because it considers the exact timestamps of requests.

The constructor defines how many requests are allowed in a time window.

The allowRequest() method first removes expired timestamps from the queue.

If the remaining number of requests is within the limit, the request is allowed and timestamp is added.

Otherwise, the request is rejected.


RequestBucket Class (Optional Abstraction)


import java.util.*;

public class RequestBucket {

    private Queue timestamps = new LinkedList<>();

    public void addRequest(long timestamp) {
        timestamps.add(timestamp);
    }

    public void removeOldRequests(long currentTime, long windowSize) {

        while (!timestamps.isEmpty()
                && currentTime - timestamps.peek() > windowSize) {
            timestamps.poll();
        }
    }

    public int size() {
        return timestamps.size();
    }
}

The RequestBucket class isolates request tracking logic.

addRequest() stores a new request timestamp.

removeOldRequests() cleans up expired requests outside the time window.

size() returns the current number of active requests in the window.


Main Class


public class Main {

    public static void main(String[] args) {

        RateLimiter fixedLimiter =
                new FixedWindowRateLimiter(3, 10000);

        System.out.println(fixedLimiter.allowRequest("U1"));
        System.out.println(fixedLimiter.allowRequest("U1"));
        System.out.println(fixedLimiter.allowRequest("U1"));
        System.out.println(fixedLimiter.allowRequest("U1"));

        RateLimiter slidingLimiter =
                new SlidingWindowRateLimiter(3, 10000);

        System.out.println(slidingLimiter.allowRequest("U1"));
        System.out.println(slidingLimiter.allowRequest("U1"));
        System.out.println(slidingLimiter.allowRequest("U1"));
        System.out.println(slidingLimiter.allowRequest("U1"));
    }
}

The main method demonstrates both fixed window and sliding window rate limiting.

We simulate multiple requests from the same user and observe how the limiter blocks excess requests.


Class Diagram

RateLimiterallowRequest(userId : String) : booleanFixedWindowRateLimitermaxRequests : intwindowSizeInMillis : longstore : Map<String, WindowData>allowRequest(userId : String) : booleanSlidingWindowRateLimitermaxRequests : intwindowSizeInMillis : longstore : Map<String, RequestLog>allowRequest(userId : String) : booleanWindowDatacount : intwindowStart : longRequestLogtimestamps : Queue<Long>addRequest(timestamp : long) : voidremoveOldRequests(currentTime : long, windowSize : long) : voidsize() : intRequestBuckettimestamps : Queue<Long>addRequest(timestamp : long) : voidremoveOldRequests(currentTime : long, windowSize : long) : voidsize() : intMainmain(args : String[]) : void

Also See

Comments

Popular posts from this blog

Designing a Parking Lot - Low Level Design

Problem Statement Design a parking lot that can handle vehicles entering and leaving while managing parking across multiple floors. Each vehicle should be assigned a suitable parking spot based on its type, and the spot should be freed once the vehicle exits. The design should also support generating a ticket at entry and optionally calculating the parking fee based on the duration of stay. Asked In Companies Amazon Google Microsoft Uber Walmart Flipkart Meta PayPal Oracle Salesforce Adobe Apple Intuit LinkedIn Atlassian Functional Requirements The design should support multiple vehicle types such as bikes, cars, and trucks A vehicle must be assigned a parking spot compatible with its type A parking spot cannot be assigned to more than one vehicle at a time The parking lot should support multiple levels (floors) The design should search and allocate an availa...

Most Frequently Asked Low Level Design(LLD) Interview Questions

Below are the curated list of most commonly asked Low Level Design (LLD) interview problems. Each problem includes a short description and a link to the complete solution with code and class diagrams. Design Parking Lot System The system should handle parking for different vehicle types such as bikes, cars, and trucks. It should manage slot allocation, availability tracking, and entry/exit flow. The design also ensures efficient usage of parking space under varying load conditions. View Solution Design Elevator / Lift System The system should support multiple elevators operating across floors with request handling logic. It focuses on scheduling algorithms to minimize wait time and optimize movement. It also manages direction control and concurrent floor requests. View Solution Design Movie Ticket Booking System The system should allow users to browse movies, select shows, and book seats. It handles seat ...

Software Design Patterns for LLD Interviews: A Complete Guide

Software Design Patterns for LLD Interviews: A Complete Guide In Software Development Engineer (SDE) interviews—especially for mid-level and senior roles—low-level design (LLD) rounds assess your ability to write clean, reusable, maintainable, and extensible code. The foundation of resolving these architectural challenges lies in the standard Gang of Four (GoF) Design Patterns. Rather than memorizing theoretical definitions, interviewers expect you to apply these patterns to real-world scenarios, identifying the trade-offs of each. Below is a comprehensive guide to the 12 most frequently asked design patterns in LLD interviews, categorized by their classification (Creational, Structural, and Behavioral). Each pattern contains a concrete, real-world Java implementation and a detailed breakdown of design decisions. Creational Design Patterns Creational design patterns deal with object creation mechanisms. They abstract the instantiation process, making a system independent of how...