Loading...

12-Hour Money-Back Guarantee

Design (LLD) Rate Limiter - C++

Design (LLD) Rate Limiter - C++

Design (LLD) Rate Limiter - C++

2 Mar 202612 min read

📌 1. Functional Requirements

A Rate Limiter system should:

  • Limit requests per client/user

  • Support multiple rate limiting algorithms

  • Allow dynamic algorithm switching

  • Support different limits per client

  • Return Allow / Deny decision

  • Track usage statistics

  • Log rate limit violations

  • Be extensible for distributed systems

📌 2. Non-Functional Requirements

  • High performance (O(1) where possible)

  • Extensible architecture

  • Clean separation of concerns

  • Algorithm pluggability

  • Easy to test

📌 3. Supported Algorithms

We implement four major algorithms:

1️⃣ Fixed Window Counter

Idea: Allow N requests per time window.

Example:
Limit = 3 requests
Window = 10 seconds

After 3 requests → deny until window resets.

⏱ Complexity

  • Time: O(1)

  • Space: O(n clients)

2️⃣ Sliding Window Log

Idea: Store timestamps of each request.

Before allowing:

  • Remove old timestamps

  • Check remaining count

⏱ Complexity

  • Time: O(k) (k = requests in window)

  • Space: O(n × k)

3️⃣ Token Bucket

Idea:

  • Tokens refill over time

  • Each request consumes 1 token

  • If no token → deny

⏱ Complexity

  • Time: O(1)

  • Space: O(n)

4️⃣ Leaky Bucket

Idea:

  • Requests enter queue

  • Requests leak at constant rate

  • If queue full → deny

⏱ Complexity

  • Time: O(1)

  • Space: O(n × k)

📌 4. Design Patterns Used

Pattern Where Used Why
Strategy Algorithms Switch rate limiting strategy dynamically
Factory Method Create limiter Encapsulate creation logic
Singleton Manager One global configuration
Observer Logging Notify on rate limit exceed
Decorator Logging wrapper Add behavior without modifying core
Builder Config creation Clean configuration building
Repository Client storage Abstract client data layer
Command Request execution Encapsulate request action
Template Method BaseRateLimiter Standardize flow

📌 5. Class Design

classDiagram
    class Observer {
        <<abstract>>
        +update(string message) void
    }
    class Logger {
        +update(string message) void
    }
    Observer <|-- Logger

    class RateLimiterConfig {
        +int limit
        +int windowSize
    }

    class RateLimiterConfigBuilder {
        -RateLimiterConfig config
        +setLimit(int) Builder
        +setWindowSize(int) Builder
        +build() RateLimiterConfig
    }
    RateLimiterConfigBuilder --> RateLimiterConfig : creates

    class BaseRateLimiter {
        <<abstract>>
        #RateLimiterConfig config
        +BaseRateLimiter(RateLimiterConfig cfg)
        +allowRequest(string id) bool
        +process(string id) bool
    }
    BaseRateLimiter *-- RateLimiterConfig : contains

    class FixedWindowLimiter {
        -Map_Counter counter
        +process(string id) bool
    }
    BaseRateLimiter <|-- FixedWindowLimiter

    class SlidingWindowLimiter {
        -Map_Logs logs
        +process(string id) bool
    }
    BaseRateLimiter <|-- SlidingWindowLimiter

    class TokenBucketLimiter {
        -Map_Buckets buckets
        +process(string id) bool
    }
    BaseRateLimiter <|-- TokenBucketLimiter

    class LeakyBucketLimiter {
        -Map_Buckets buckets
        +process(string id) bool
    }
    BaseRateLimiter <|-- LeakyBucketLimiter

    class RateLimiterFactory {
        +createLimiter(string type, Config cfg) BaseLimiter
    }
    RateLimiterFactory --> BaseRateLimiter : creates

    class RateLimiterDecorator {
        #BaseLimiter limiter
        +RateLimiterDecorator(BaseLimiter l)
    }
    BaseRateLimiter <|-- RateLimiterDecorator
    RateLimiterDecorator o-- BaseRateLimiter : decorates

    class LoggingDecorator {
        -Observer observer
        +LoggingDecorator(BaseLimiter l, Observer obs)
        +process(string id) bool
    }
    RateLimiterDecorator <|-- LoggingDecorator
    LoggingDecorator --> Observer : notifies

    class ClientRepository {
        -Map_Clients clients
        +addClient(string id) void
        +exists(string id) bool
    }

    class Command {
        <<abstract>>
        +execute() void
    }
    class RequestCommand {
        -BaseLimiter limiter
        -string id
        +RequestCommand(BaseLimiter l, string id)
        +execute() void
    }
    Command <|-- RequestCommand
    RequestCommand --> BaseRateLimiter : uses

    class RateLimiterManager {
        -BaseLimiter limiter
        -RateLimiterManager()
        +getInstance() Manager
        +setLimiter(BaseLimiter l) void
        +getLimiter() BaseLimiter
    }
    RateLimiterManager o-- BaseRateLimiter : holds

📌 6. Complete C++ Implementation (Single Threaded)

⚠️ This is intentionally single-threaded for machine coding clarity.

🔹 1. Observer Pattern (Logger)

class Observer {
public:
    virtual void update(string message) = 0;
    virtual ~Observer() {}
};

class Logger : public Observer {
public:
    void update(string message) override {
        cout << "[LOG]: " << message << endl;
    }
};

🔹 2. Builder Pattern (Configuration)

class RateLimiterConfig {
public:
    int limit;
    int windowSize;
};

class RateLimiterConfigBuilder {
private:
    RateLimiterConfig config;

public:
    RateLimiterConfigBuilder& setLimit(int l) {
        config.limit = l;
        return *this;
    }

    RateLimiterConfigBuilder& setWindowSize(int w) {
        config.windowSize = w;
        return *this;
    }

    RateLimiterConfig build() {
        return config;
    }
};

🔹 3. Template Method Pattern

class BaseRateLimiter {
protected:
    RateLimiterConfig config;

public:
    BaseRateLimiter(RateLimiterConfig cfg) : config(cfg) {}

    bool allowRequest(string clientId) {
        return process(clientId);
    }

    virtual bool process(string clientId) = 0;
    virtual ~BaseRateLimiter() {}
};

📌 7. Strategy Pattern (All Algorithms)

🔹 Fixed Window

class FixedWindowLimiter : public BaseRateLimiter {
private:
    unordered_map<string, pair<int, time_t>> counter;

public:
    FixedWindowLimiter(RateLimiterConfig cfg)
        : BaseRateLimiter(cfg) {}

    bool process(string clientId) override {
        time_t now = time(nullptr);

        if(counter[clientId].second + config.windowSize <= now) {
            counter[clientId] = {1, now};
            return true;
        }

        if(counter[clientId].first < config.limit) {
            counter[clientId].first++;
            return true;
        }

        return false;
    }
};

🔹 Sliding Window

class SlidingWindowLimiter : public BaseRateLimiter {
private:
    unordered_map<string, vector<time_t>> logs;

public:
    SlidingWindowLimiter(RateLimiterConfig cfg)
        : BaseRateLimiter(cfg) {}

    bool process(string clientId) override {
        time_t now = time(nullptr);
        auto& timestamps = logs[clientId];

        timestamps.erase(remove_if(timestamps.begin(), timestamps.end(),
            [&](time_t t) { return t <= now - config.windowSize; }),
            timestamps.end());

        if(timestamps.size() < config.limit) {
            timestamps.push_back(now);
            return true;
        }

        return false;
    }
};

🔹 Token Bucket

class TokenBucketLimiter : public BaseRateLimiter {
private:
    struct Bucket {
        int tokens = 0;
        time_t lastRefill = 0;
    };

    unordered_map<string, Bucket> buckets;

public:
    TokenBucketLimiter(RateLimiterConfig cfg)
        : BaseRateLimiter(cfg) {}

    bool process(string clientId) override {
        time_t now = time(nullptr);
        auto& bucket = buckets[clientId];

        if(bucket.tokens == 0) {
            bucket.tokens = config.limit;
            bucket.lastRefill = now;
        }

        int refill = now - bucket.lastRefill;
        if(refill > 0) {
            bucket.tokens = min(config.limit, bucket.tokens + refill);
            bucket.lastRefill = now;
        }

        if(bucket.tokens > 0) {
            bucket.tokens--;
            return true;
        }

        return false;
    }
};

🔹 Leaky Bucket

class LeakyBucketLimiter : public BaseRateLimiter {
private:
    struct Bucket {
        queue<time_t> requests;
    };

    unordered_map<string, Bucket> buckets;

public:
    LeakyBucketLimiter(RateLimiterConfig cfg)
        : BaseRateLimiter(cfg) {}

    bool process(string clientId) override {
        time_t now = time(nullptr);
        auto& bucket = buckets[clientId];

        while(!bucket.requests.empty() &&
              bucket.requests.front() <= now - config.windowSize) {
            bucket.requests.pop();
        }

        if(bucket.requests.size() < config.limit) {
            bucket.requests.push(now);
            return true;
        }

        return false;
    }
};

📌 8. Factory Pattern

class RateLimiterFactory {
public:
    static shared_ptr<BaseRateLimiter> createLimiter(
        string type, RateLimiterConfig config) {

        if(type == "FIXED")
            return make_shared<FixedWindowLimiter>(config);
        if(type == "SLIDING")
            return make_shared<SlidingWindowLimiter>(config);
        if(type == "TOKEN")
            return make_shared<TokenBucketLimiter>(config);
        if(type == "LEAKY")
            return make_shared<LeakyBucketLimiter>(config);

        return nullptr;
    }
};

📌 9. Decorator Pattern (Logging)

class RateLimiterDecorator : public BaseRateLimiter {
protected:
    shared_ptr<BaseRateLimiter> limiter;

public:
    RateLimiterDecorator(shared_ptr<BaseRateLimiter> l)
        : BaseRateLimiter(l->config), limiter(l) {}
};

class LoggingDecorator : public RateLimiterDecorator {
private:
    Observer* observer;

public:
    LoggingDecorator(shared_ptr<BaseRateLimiter> l, Observer* obs)
        : RateLimiterDecorator(l), observer(obs) {}

    bool process(string clientId) override {
        bool allowed = limiter->allowRequest(clientId);

        if(!allowed)
            observer->update("Rate limit exceeded for: " + clientId);

        return allowed;
    }
};

📌 10. Repository Pattern

class ClientRepository {
private:
    unordered_map<string, int> clients;

public:
    void addClient(string id) {
        clients[id] = 1;
    }

    bool exists(string id) {
        return clients.count(id);
    }
};

📌 11. Command Pattern

class Command {
public:
    virtual void execute() = 0;
    virtual ~Command() {}
};

class RequestCommand : public Command {
private:
    shared_ptr<BaseRateLimiter> limiter;
    string clientId;

public:
    RequestCommand(shared_ptr<BaseRateLimiter> l, string id)
        : limiter(l), clientId(id) {}

    void execute() override {
        bool allowed = limiter->allowRequest(clientId);
        cout << "Request for " << clientId
             << (allowed ? " Allowed" : " Denied") << endl;
    }
};

📌 12. Singleton Manager

class RateLimiterManager {
private:
    shared_ptr<BaseRateLimiter> limiter;

    RateLimiterManager() {}

public:
    static RateLimiterManager& getInstance() {
        static RateLimiterManager instance;
        return instance;
    }

    void setLimiter(shared_ptr<BaseRateLimiter> l) {
        limiter = l;
    }

    shared_ptr<BaseRateLimiter> getLimiter() {
        return limiter;
    }
};

📌 13. Main Function

int main() {

    RateLimiterConfig config =
        RateLimiterConfigBuilder()
            .setLimit(3)
            .setWindowSize(10)
            .build();

    auto limiter = RateLimiterFactory::createLimiter("SLIDING", config);

    Logger logger;
    auto decoratedLimiter =
        make_shared<LoggingDecorator>(limiter, &logger);

    RateLimiterManager::getInstance().setLimiter(decoratedLimiter);

    string client = "clientA";

    for(int i = 0; i < 5; i++) {
        RequestCommand cmd(
            RateLimiterManager::getInstance().getLimiter(),
            client
        );
        cmd.execute();
    }

    return 0;
}

Complete running code

#include <iostream>
#include <unordered_map>
#include <queue>
#include <vector>
#include <memory>
#include <ctime>

using namespace std;

//////////////////////////////////////////////////////////////
// OBSERVER PATTERN
//////////////////////////////////////////////////////////////

class Observer {
public:
    virtual void update(string message) = 0;
    virtual ~Observer() {}
};

class Logger : public Observer {
public:
    void update(string message) override {
        cout << "[LOG]: " << message << endl;
    }
};

//////////////////////////////////////////////////////////////
// BUILDER PATTERN
//////////////////////////////////////////////////////////////

class RateLimiterConfig {
public:
    int limit;
    int windowSize;
};

class RateLimiterConfigBuilder {
private:
    RateLimiterConfig config;

public:
    RateLimiterConfigBuilder& setLimit(int l) {
        config.limit = l;
        return *this;
    }

    RateLimiterConfigBuilder& setWindowSize(int w) {
        config.windowSize = w;
        return *this;
    }

    RateLimiterConfig build() {
        return config;
    }
};

//////////////////////////////////////////////////////////////
// TEMPLATE METHOD PATTERN
//////////////////////////////////////////////////////////////

class BaseRateLimiter {
protected:
    RateLimiterConfig config;

public:
    BaseRateLimiter(RateLimiterConfig cfg) : config(cfg) {}

    bool allowRequest(string clientId) {
        return process(clientId);
    }

    virtual bool process(string clientId) = 0;
    virtual ~BaseRateLimiter() {}
};

//////////////////////////////////////////////////////////////
// STRATEGY PATTERN (Algorithms)
//////////////////////////////////////////////////////////////

class FixedWindowLimiter : public BaseRateLimiter {
private:
    unordered_map<string, pair<int, time_t>> counter;

public:
    FixedWindowLimiter(RateLimiterConfig cfg)
        : BaseRateLimiter(cfg) {}

    bool process(string clientId) override {
        time_t now = time(nullptr);

        if(counter[clientId].second + config.windowSize <= now) {
            counter[clientId] = {1, now};
            return true;
        }

        if(counter[clientId].first < config.limit) {
            counter[clientId].first++;
            return true;
        }

        return false;
    }
};

class SlidingWindowLimiter : public BaseRateLimiter {
private:
    unordered_map<string, vector<time_t>> logs;

public:
    SlidingWindowLimiter(RateLimiterConfig cfg)
        : BaseRateLimiter(cfg) {}

    bool process(string clientId) override {
        time_t now = time(nullptr);
        auto& timestamps = logs[clientId];

        timestamps.erase(remove_if(timestamps.begin(), timestamps.end(),
            [&](time_t t) { return t <= now - config.windowSize; }),
            timestamps.end());

        if(timestamps.size() < config.limit) {
            timestamps.push_back(now);
            return true;
        }

        return false;
    }
};

class TokenBucketLimiter : public BaseRateLimiter {
private:
    struct Bucket {
        int tokens;
        time_t lastRefill;
    };

    unordered_map<string, Bucket> buckets;

public:
    TokenBucketLimiter(RateLimiterConfig cfg)
        : BaseRateLimiter(cfg) {}

    bool process(string clientId) override {
        time_t now = time(nullptr);
        auto& bucket = buckets[clientId];

        if(bucket.tokens == 0) {
            bucket.tokens = config.limit;
            bucket.lastRefill = now;
        }

        int refill = (now - bucket.lastRefill);
        if(refill > 0) {
            bucket.tokens = min(config.limit, bucket.tokens + refill);
            bucket.lastRefill = now;
        }

        if(bucket.tokens > 0) {
            bucket.tokens--;
            return true;
        }

        return false;
    }
};

class LeakyBucketLimiter : public BaseRateLimiter {
private:
    struct Bucket {
        queue<time_t> requests;
    };

    unordered_map<string, Bucket> buckets;

public:
    LeakyBucketLimiter(RateLimiterConfig cfg)
        : BaseRateLimiter(cfg) {}

    bool process(string clientId) override {
        time_t now = time(nullptr);
        auto& bucket = buckets[clientId];

        while(!bucket.requests.empty() &&
              bucket.requests.front() <= now - config.windowSize) {
            bucket.requests.pop();
        }

        if(bucket.requests.size() < config.limit) {
            bucket.requests.push(now);
            return true;
        }

        return false;
    }
};

//////////////////////////////////////////////////////////////
// FACTORY METHOD
//////////////////////////////////////////////////////////////

class RateLimiterFactory {
public:
    static shared_ptr<BaseRateLimiter> createLimiter(
        string type, RateLimiterConfig config) {

        if(type == "FIXED")
            return make_shared<FixedWindowLimiter>(config);
        if(type == "SLIDING")
            return make_shared<SlidingWindowLimiter>(config);
        if(type == "TOKEN")
            return make_shared<TokenBucketLimiter>(config);
        if(type == "LEAKY")
            return make_shared<LeakyBucketLimiter>(config);

        return nullptr;
    }
};

//////////////////////////////////////////////////////////////
// DECORATOR PATTERN (Logging)
//////////////////////////////////////////////////////////////

class RateLimiterDecorator : public BaseRateLimiter {
protected:
    shared_ptr<BaseRateLimiter> limiter;

public:
    RateLimiterDecorator(shared_ptr<BaseRateLimiter> l)
        : BaseRateLimiter(l->config), limiter(l) {}
};

class LoggingDecorator : public RateLimiterDecorator {
private:
    Observer* observer;

public:
    LoggingDecorator(shared_ptr<BaseRateLimiter> l, Observer* obs)
        : RateLimiterDecorator(l), observer(obs) {}

    bool process(string clientId) override {
        bool allowed = limiter->allowRequest(clientId);

        if(!allowed)
            observer->update("Rate limit exceeded for: " + clientId);

        return allowed;
    }
};

//////////////////////////////////////////////////////////////
// REPOSITORY PATTERN
//////////////////////////////////////////////////////////////

class ClientRepository {
private:
    unordered_map<string, int> clients;

public:
    void addClient(string id) {
        clients[id] = 1;
    }

    bool exists(string id) {
        return clients.count(id);
    }
};

//////////////////////////////////////////////////////////////
// COMMAND PATTERN
//////////////////////////////////////////////////////////////

class Command {
public:
    virtual void execute() = 0;
    virtual ~Command() {}
};

class RequestCommand : public Command {
private:
    shared_ptr<BaseRateLimiter> limiter;
    string clientId;

public:
    RequestCommand(shared_ptr<BaseRateLimiter> l, string id)
        : limiter(l), clientId(id) {}

    void execute() override {
        bool allowed = limiter->allowRequest(clientId);
        cout << "Request for " << clientId
             << (allowed ? " Allowed" : " Denied") << endl;
    }
};

//////////////////////////////////////////////////////////////
// SINGLETON MANAGER
//////////////////////////////////////////////////////////////

class RateLimiterManager {
private:
    shared_ptr<BaseRateLimiter> limiter;

    RateLimiterManager() {}

public:
    static RateLimiterManager& getInstance() {
        static RateLimiterManager instance;
        return instance;
    }

    void setLimiter(shared_ptr<BaseRateLimiter> l) {
        limiter = l;
    }

    shared_ptr<BaseRateLimiter> getLimiter() {
        return limiter;
    }
};

//////////////////////////////////////////////////////////////
// MAIN
//////////////////////////////////////////////////////////////

int main() {

    RateLimiterConfig config =
        RateLimiterConfigBuilder()
            .setLimit(3)
            .setWindowSize(10)
            .build();

    auto limiter = RateLimiterFactory::createLimiter("SLIDING", config);

    Logger logger;
    auto decoratedLimiter =
        make_shared<LoggingDecorator>(limiter, &logger);

    RateLimiterManager::getInstance().setLimiter(decoratedLimiter);

    string client = "clientA";

    for(int i = 0; i < 5; i++) {
        RequestCommand cmd(
            RateLimiterManager::getInstance().getLimiter(),
            client
        );
        cmd.execute();
    }

    return 0;
}

📌 14. Complexity Summary

Algorithm Time Space
Fixed Window O(1) O(n)
Sliding Window O(k) O(n×k)
Token Bucket O(1) O(n)
Leaky Bucket O(1) O(n×k)

📌 15. Interview-Level Talking Points

✔ Why Strategy? → Switch algorithms dynamically

✔ Why Template Method? → Standardize request flow

✔ Why Decorator? → Add logging without modifying core

✔ Why Builder? → Clean configuration

✔ Why Factory? → Decouple creation

✔ Why Singleton? → Global configuration control

🚨 Multithreading Problems in the Above Rate Limiter Implementation

Our current implementation is single-threaded.

If multiple threads call:

allowRequest(clientId);

simultaneously → the system becomes unsafe.

Below is a complete breakdown of all concurrency problems and their proper solutions.

🔴 PROBLEM 1: Data Race on unordered_map

Where?

In every algorithm:

unordered_map<string, ...> logs;
unordered_map<string, ...> counter;
unordered_map<string, Bucket> buckets;

Why It’s Dangerous?

unordered_map is NOT thread-safe.

Two threads doing:

logs[clientId]

can:

  • Corrupt internal bucket structure

  • Cause undefined behavior

  • Crash program