throttled-py gives Python rate limiting five algorithms and two backends
🔧 High-performance Python rate limiting library with multiple algorithms (Fixed Window, Sliding Window, Token Bucket, Leaky Bucket & GCRA) and storage backends (Redis, In-Memory).
At a glance
- What is it?
- throttled-py is a Python library for rate limiting that ships Fixed Window, Sliding Window, Token Bucket, Leaky Bucket and GCRA algorithms over Redis or in-memory storage, with sync and async APIs, decorator and context manager styles, and official FastAPI and Flask integrations. The package has no required dependencies, which is the unusual part.
- Who is it for?
- Adopt throttled-py if you need rate limiting in a Python service and you want to choose between GCRA, token bucket and window algorithms, over Redis when you have several processes and in-memory when you have one. Do not adopt it as a global gateway limiter, since it is a library you call inside your application and not a proxy in front of it, and do not assume the in-memory backend gives you a shared limit across workers.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Five algorithms over Redis or memory, with no required dependencies
throttled-py is a rate limiting library for Python, and its selling point is the breadth of the algorithm set rather than any single technique. Five algorithms are supported: Fixed Window, Sliding Window, Token Bucket, Leaky Bucket and GCRA, the Generic Cell Rate Algorithm.
That list matters because rate limiting is one of the few places where the algorithm choice changes the shape of the traffic your service sees. A fixed window permits a burst at the boundary between two windows, a sliding window does not but costs more to evaluate, a token bucket allows bursts up to a bucket size while enforcing an average rate, a leaky bucket smooths output into a constant flow, and GCRA is the one used by the IETF rate limit headers that the FastAPI integration emits.
The storage side has two options, and both are documented as thread safe. Redis is supported in standalone, Sentinel and Cluster form, and there is an in-memory backend with support for key expiration and eviction. The in-memory backend is the interesting default for a library, because it means a single process needs no external service to rate limit, and the Redis backend is there for the case where the limit has to be shared.
The packaging is what makes this project unusual. The pyproject.toml lists dependencies = [], an empty list, and the project classifiers describe it as Development Status 5 - Production/Stable. Since v2.0.0 only core dependencies are installed by default, and everything else arrives as an extra.
The licence is MIT, the home page is throttled-py.readthedocs.io, and there is a Chinese README alongside the English one, so the project is maintained with both audiences in mind.
limit takes a request, peek just looks
The core API is two methods, and the distinction between them is the one that matters when you are writing a middleware.
limit deducts a request and returns a RateLimitResult. peek checks the current rate limit state for a key and returns a RateLimitState without consuming anything. That separation is the difference between a limiter and a monitor, and it means you can expose the remaining quota in a response header or a dashboard without spending a token to do it.
Around that core sit the call styles. The README lists function call, decorator and context manager modes, so the library can be used as a plain function in a handler, wrapped around a function definition, or entered with a with block. The decorator form is what the FastAPI and Flask integrations build on.
And there are two response modes. Immediate response mode rejects a request when the limit is already reached, and wait-retry mode holds the request until capacity is available or the wait fails. Wait-retry is the one that changes the shape of your service: instead of returning a 429, the call blocks, which means a thread or a coroutine is occupied for the duration of the wait, and the number of concurrent requests your process can hold becomes part of your capacity planning rather than something the limiter hides from you.
The quotas themselves are configurable rather than hard coded, and role selection can be based on user-defined tags as well as auto-discovered ones, so you can vary the limit per API key, per tenant or per endpoint without a separate limiter instance for each case.
Installing the core package and the extras you need
The base install is a single pip command:
$ pip install throttled-pyThe README carries an important note about versions. v3.x requires Python 3.10 or newer, and if you are on Python 3.8 or 3.9 you should install throttled-py<3.0.0. The package metadata agrees, with requires-python set to >=3.10 and classifiers for 3.10 through 3.14, plus a PyPy implementation classifier.
Everything beyond the core library is an extra, and the README gives the exact commands:
$ pip install "throttled-py[redis]"
$ pip install "throttled-py[otel]"
$ pip install "throttled-py[fastapi]"
$ pip install "throttled-py[flask]"Multiple extras can be comma-separated, for example pip install "throttled-py[redis,otel]".
The extras table lists what each one buys. The memory extra installs no additional dependencies at all, because the in-memory backend is available by default. The redis extra pulls in the redis client, with the constraint in pyproject.toml of redis>3,!=4.5.2,!=4.5.3,<8.0.0, which excludes two specific broken patch releases. The otel extra adds the OpenTelemetry API at 1.30.0 or later. The fastapi extra requires fastapi>=0.100.0,<1.0.0 and the flask extra requires flask>=2.3.3,<4.0.
That version pinning is worth reading rather than skipping, because it tells you which combinations are tested. The redis exclusion of 4.5.2 and 4.5.3 is the kind of detail that only appears when someone hit a real problem with those releases.
The build backend is hatchling, and the repository carries a Makefile with targets for install, build, publishing to PyPI and to the test index, and a docker-build-local target that builds an image tagged with the Python version and a Tencent mirror repository name.
The web framework integrations and the IETF headers
The framework integrations are where a rate limiter either becomes part of your application or stays a library you call by hand, and this project has done the work for both FastAPI and Flask.
The FastAPI integration is described as official, with async decorator-based rate limiting, IETF compliant RateLimit-* headers, and HTTP 429 handling. The IETF part is the substantive bit. Those headers are the standard way of telling a client how much quota it has left and when it resets, and if you emit them correctly, well behaved clients can back off on their own instead of hammering you and getting rejected. The framework integrations also handle the 429 response, which removes the most common source of inconsistency, where the limiter rejects the request but the handler does not set the status code or forgets the Retry-After hint.
The Flask integration is also official, with decorator-based rate limiting, application factory support, and HTTP 429 handling. The application factory point matters for anyone who uses the factory pattern, because a limiter that has to be constructed at import time rather than inside the factory does not fit that structure.
There is also an integration with the Model Context Protocol Python SDK, described as providing rate limiting support for model dialog processes. That is a small, specific use case, and it is a sign of where the author is working: the same primitives, applied to a new class of traffic.
Note that all of this is decorator-based. The library does not install a proxy or middleware in front of your service; it gives you a way to rate limit inside the application, and the framework integrations wrap that in a decorator.
What the performance numbers actually compare
The performance claim in the README is stated as a multiple of primitive operations rather than as a throughput figure, and that framing is more useful than a requests per second number would have been.
The claim is that the execution time for a single rate limiting API call is equivalent to roughly 2.5 to 4.5 times a dict[key] += 1 operation for the in-memory backend, and roughly 1.06 to 1.37 times an INCRBY key increment operation for Redis.
Read carefully, that says two different things. The Redis number is near the floor: the limiter costs about as much as the single Redis command it has to issue anyway, which is what you would hope for, because the network round trip dominates. The in-memory number is the more interesting one, because the in-memory backend is doing more work than a bare counter: it is applying the algorithm's semantics, tracking window state, and handling key expiration and eviction. Four times a dictionary increment is cheap in absolute terms, and the point of the comparison is that the algorithm does not dominate the request path.
The README links to a Benchmarks section for the detail, and the repository has a codecov configuration and a coverage badge, so the project is measuring itself. The honest caveat is that these are the author's own numbers on the author's own workloads, and the multiples are the kind of figure that changes with the algorithm chosen, the key cardinality, and how much of the key space is being touched.
What the claim does tell you is which backend suits which situation. If your limiter runs in-process and the algorithm is cheap relative to your request handling, the in-memory path is unlikely to be your bottleneck. If it is shared across processes, the Redis cost is one extra round trip, and the question becomes whether your limit needs to be globally accurate.
Choosing between the in-memory and Redis backends
The backend choice is the decision that will shape your deployment, and it is worth being explicit about the trade-off rather than assuming the more capable option is the right one.
The in-memory backend keeps counters in the process, with key expiration and eviction support, and it needs no external service. That makes it the right default for a single process, for a development environment, and for a service where an approximate limit is acceptable. It is also the fastest, because there is no network involved at all.
The problem with in-memory is scope. Each process has its own counters, so if you run four workers behind a load balancer, a limit of 100 requests per minute per key becomes 400 across the cluster. Whether that matters depends on what the limit is for. If it is protecting a downstream dependency with a hard quota, the answer is that it matters a great deal. If it is shaping traffic to keep one service's latency in check, a per-process limit might be an acceptable approximation.
Redis, in standalone, Sentinel or Cluster form, moves the counter out of the process and makes the limit global. The cost is the round trip on every check, which the benchmark says is comparable to the INCRBY the limiter would issue anyway, plus the operational surface of running Redis.
There is a third consideration that the README does not spend much time on, which is the eviction and expiration behaviour of the in-memory store. A limiter that keeps a key per user, per endpoint, with no expiry, is a memory leak with a rate limit attached, and the key expiration and eviction support is what prevents that. If you are using the in-memory backend with high key cardinality, that behaviour is the part to verify first.
Maintenance, packaging and the limits of the approach
The project is moving. The release history shows v3.4.0 on 2026-06-15, v3.4.1 on 2026-08-01 and v3.5.0 on 2026-08-30, and the last push was on 2026-10-18, with the version in pyproject.toml at 3.5.0. There is a CHANGELOG.rst alongside an English CHANGELOG_EN.rst, an AGENTS.md file, a pre-commit configuration, a Read the Docs configuration, and an examples/ directory with a quickstart and a contrib folder.
The library is classified as Production/Stable, which is about the package rather than the rate limiting model itself.
The more useful boundary is what a library like this cannot do. It is not a gateway. It does not sit in front of your service, it does not see traffic you have not routed through your code, and it does not protect a database from a client that never reaches your handler. If you need a limit that applies to every connection regardless of the application, that is a proxy or a network level control, and no amount of decorator configuration changes it.
There is also the classic correctness point about client identity. A rate limiter keyed on a request parameter can be trivially sidestepped by a client that changes that parameter, so the key you choose, whether an account id, an API key, a token subject or an IP address, is the security decision, and the library will happily use whichever you give it.
The MIT licence means you can vendor it, patch it or wrap it without copyleft obligations, which is the practical consideration for a dependency this small and this central to a request path.
Editorial conclusion
Adopt throttled-py if you need rate limiting in a Python service and you want to choose between GCRA, token bucket and window algorithms, over Redis when you have several processes and in-memory when you have one. Do not adopt it as a global gateway limiter, since it is a library you call inside your application and not a proxy in front of it, and do not assume the in-memory backend gives you a shared limit across workers. Before you start, check your Python version against the v3 floor of 3.10, decide whether your limit is per process or global, and read the benchmark page rather than taking the headline multiples as a promise about your own traffic.
Frequently asked questions
How do I install throttled-py?
Run `pip install throttled-py` for the core library, which has no required dependencies. Extras are added with names such as "throttled-py[redis]", "throttled-py[otel]", "throttled-py[fastapi]" and "throttled-py[flask]", and multiple extras can be comma-separated. Version 3.x requires Python 3.10 or newer.
Which rate limiting algorithms does throttled-py support?
Fixed Window, Sliding Window, Token Bucket, Leaky Bucket and GCRA, the Generic Cell Rate Algorithm. The algorithms are configurable, and quota configuration is flexible enough to vary limits by role or tag.
What is the difference between limit and peek in throttled-py?
limit deducts a request and returns a RateLimitResult, while peek checks the current state for a key without consuming anything and returns a RateLimitState. Peeking is what you use when you want to report remaining quota without spending any.
Does throttled-py share limits between processes?
Only if you use the Redis backend, because standalone, Sentinel and Cluster Redis store the counters outside the process. The in-memory backend keeps counters in the process, so each worker or process enforces its own limit independently.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zhuozhuocrayon-throttled-py)Was this useful?