Comparing Two Ways to Use “scope” in DRF Throttling: ScopedRateThrottle vs Inheriting UserRateThrottle

When applying scope‑based throttling in DRF, two common patterns frequently appear.

  • Pattern A: Inherit from UserRateThrottle (or SimpleRateThrottle), create a custom class with a fixed scope, and attach it to the view via throttle_classes.
  • Pattern B: Register ScopedRateThrottle in DEFAULT_THROTTLE_CLASSES and simply set throttle_scope = "login" on the view.

Do they produce the same effect?

If you only need a single rate limit based on user ID (or unauthenticated IP) + scope, the outcomes are almost identical. Differences arise in how the throttling is applied (error prevention), extensibility (multiple policies / custom criteria), and code structure (settings‑centric vs code‑centric).


Commonality: Both build a key from scope + (user ID or IP)

  • ScopedRateThrottle constructs a key using throttle_scope (if present) plus the user ID or IP, then looks up the corresponding rate in DEFAULT_THROTTLE_RATES.
  • UserRateThrottle also limits by user ID for authenticated users and by IP for unauthenticated ones. To support multiple scopes, DRF recommends subclassing the throttle and assigning different scope values.

Difference 1: Where the scope is declared – view attribute vs class attribute

Pattern A: Scope fixed in the class

from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView

class CustomDomainSaveThrottle(UserRateThrottle):
    scope = "custom_domain_save"

class SaveCustomDomainView(APIView):
    throttle_classes = [CustomDomainSaveThrottle]
  • The scope is hard‑coded in the class.
  • The view only needs to reference the class.
  • Reduces the chance of typos in the scope string across multiple views.

Pattern B: Scope set on the view, interpreted by a shared throttle

REST_FRAMEWORK = {
  "DEFAULT_THROTTLE_CLASSES": [
    "rest_framework.throttling.ScopedRateThrottle",
  ],
  "DEFAULT_THROTTLE_RATES": {
    "login": "5/min",
  }
}

from rest_framework.views import APIView

class LoginView(APIView):
    throttle_scope = "login"
  • The scope lives directly on the view.
  • The shared ScopedRateThrottle checks the view for throttle_scope and applies the rate from settings.
  • Keeps code concise and centralizes policy management in settings.

Difference 2: Who guarantees the application – global registration vs per‑view attachment

Misconception about Pattern B

Adding ScopedRateThrottle to DEFAULT_THROTTLE_CLASSES does not affect views that lack throttle_scope. So a global registration does not blanket‑block all APIs.

Strength of Pattern A

You can attach a throttle to specific views without touching global settings. This is handy when you cannot modify settings extensively or when you want to enforce throttling only on particular apps or endpoints.


Difference 3: Extensibility – beyond a single limit

The two patterns look similar only when each view has a single scope and a single rate. Beyond that, differences become pronounced.

1) Applying multiple throttles (burst + sustained) to one view

DRF’s documentation shows using several UserRateThrottle subclasses for burst and sustained limits.

class Burst(UserRateThrottle):
    scope = "burst"

class Sustained(UserRateThrottle):
    scope = "sustained"

class SearchView(APIView):
    throttle_classes = [Burst, Sustained]

With ScopedRateThrottle alone, expressing two scopes for one view is awkward; you would need custom logic or additional classes.

2) Changing the basis of throttling

ScopedRateThrottle operates on user ID / IP by default. Real‑world scenarios often require:

  • IP + username for login (to mitigate account‑targeted attacks)
  • tenant_id for multi‑tenant services
  • API key for key‑based limits

These cases necessitate overriding get_cache_key() or similar logic, which naturally leads to Pattern A (subclassing / custom throttle). DRF also encourages custom throttles for such needs.


Two keys opening an API connection

Which pattern should you choose?

For most "view‑specific rate limits" → Pattern B (ScopedRateThrottle)

  • Minimal code changes
  • Central policy management via settings
  • No effect on views without throttle_scope

If any of the following apply → Pattern A (inherit / custom class)

  • You need multiple limits (burst + sustained) on a single view
  • The throttling basis is not user ID / IP (e.g., username+IP, tenant, API key)
  • You cannot modify global settings and want to enforce throttling only on selected views
  • You prefer documenting the policy through the class name

Related article: