Skip to main content

Overview

While most rate limits are defined statically in the RateLimiter constructor, you can also define them dynamically at runtime using the config parameter.

Static vs Dynamic Definitions

Define rate limits in the constructor for type safety:

Dynamic Definition

Define rate limits inline when calling limit():

When to Use Dynamic Limits

1. One-Off Rate Limits

For rarely-used rate limits that don’t warrant a static definition:

2. User-Specific Limits

Different rate limits based on user tier or subscription:

3. Configuration from Database

Rate limits stored in your database:

4. Time-Based Limits

Different limits during peak vs off-peak hours:

5. A/B Testing Rate Limits

Test different rate limit configurations:

Type Safety with Dynamic Limits

When using dynamic configs, TypeScript requires the config parameter:
From the source (client/index.ts:307-322):

Combining Static and Dynamic

You can override static configs with dynamic ones:
Warning: Overriding a static config can be confusing. Consider using a different name for truly different rate limits.

Pattern: Rate Limit Factory

Create reusable rate limit configurations:

Dynamic Limits with React Hooks

You can also use dynamic configs with the useRateLimit hook:

Best Practices

  1. Prefer static definitions: Use the constructor for most rate limits
  2. Document dynamic configs: Comment why a dynamic config is needed
  3. Validate dynamic configs: Ensure rate/period values are reasonable
  4. Cache configs: Don’t recalculate on every request if possible
  5. Consider maintenance: Dynamic configs are harder to audit and update

Validation Example

When NOT to Use Dynamic Limits

Avoid dynamic limits when:
  • The rate limit is used frequently (define it statically)
  • You need strong type safety
  • The configuration rarely changes
  • Multiple parts of your code use the same limit

Performance Considerations

Dynamic configs have minimal overhead, but:
  1. Database lookups add latency: Fetching configs from the database takes time
  2. No caching by default: Each request recalculates the config
  3. Type checking is runtime: TypeScript can’t validate dynamic configs at compile time
Consider caching frequently-used dynamic configs:
For more advanced patterns, see Sharding for high-throughput scenarios and Reservations for preventing starvation.