Design an API Rate Limiter
A server-side rate limiting system that protects APIs from abuse using sliding window counters and Redis-backed distributed state.
Traditional n-tier architecture organizing code into horizontal layers with strict dependency flow.
Rough, order-of-magnitude numbers. The point is to justify the architecture, not to be exact.
A diagram shows what exists; a trace shows what happens. Press play on a flow, or click any step, and the diagram above lights up the exact path that request takes.
The one path every request takes, in both directions.
The rule this pattern enforces, and what it costs to keep it.
Every box on the canvas, and the job it does. Click a card to locate it in the diagram; click the node itself for the full deep dive.
Layered Architecture organizes an application into horizontal tiers, each depending only on the tier directly beneath it. It is the most traditional way to structure a monolith, and its main virtue is predictability: any request's path through the codebase is always the same fixed sequence of hops, in the same order, with no exceptions to memorize.
The entire value of this pattern rests on one constraint: no layer skips a layer. The API Layer never queries the Database directly, and the Presentation Layer never touches Persistence, even when a shortcut looks harmless. Enforcing this consistently is what keeps the dependency graph simple enough to reason about — the moment shortcuts are allowed for convenience, the graph stops being layered at all.
Strict layering buys simplicity and a single deployable unit at the cost of two things: every request pays for the full hop chain even when most of it is unnecessary overhead, and the whole application has to scale together even if only one layer — usually the database — is actually under load. Those costs are exactly what patterns like CQRS or hexagonal architecture exist to relax, once a system outgrows this one's assumptions.
An architecture is a record of trade-offs. For every major choice here: what won, what lost, and why the constraints made it so.
With zero cross-layer shortcuts allowed, the dependency graph stays simple enough that any newcomer can trace a request's path through the codebase without exceptions to memorize. The cost is an extra hop of latency on every request — a trade this pattern makes deliberately in favor of comprehensibility.
For a team of 1-2 groups, one deploy unit means one CI pipeline, one release process, and no network calls between layers to secure or version. Microservices would buy independent scaling per layer, but this system doesn't yet have a layer whose load justifies that operational cost.
Isolating every query behind one layer means a database or ORM swap touches exactly one place in the codebase, and it confines the entire SQL-injection attack surface to code that is reviewed and tested specifically for it.
A single source of truth for business rules avoids the two copies drifting apart. When a second UI (mobile) is added later, it inherits the exact same enforced rules for free instead of re-implementing them.
Reading a system means sensing where it cracks under 10× load. These are the pressure points of this design, and how it holds.
Even a trivial read crosses Presentation, API, Business Logic, and Persistence before it ever reaches the Database. Under high load that fixed overhead adds up across every single request, not just the expensive ones.
This pattern alone doesn't solve it — it pairs well with introducing a cache in front of Persistence, or evolving toward a read-optimized pattern like CQRS once the overhead genuinely matters.
If only the Persistence Layer is under load, the entire monolith still has to be scaled (redeployed, given more instances) to handle it — wasting resources on the other four layers that were never the bottleneck.
This is the fundamental trade of layered versus microservice architectures. Extracting the hot layer into its own independently scalable service is the usual next step once it becomes a genuine, measured bottleneck.
Over time, a layer can degenerate into 'call the layer below, do nothing else' boilerplate — providing the structure of layering without enforcing any real rule or translation.
Only keep a layer if it enforces something real. Collapse a layer that has become a pure pass-through rather than preserving it out of habit.
Because every layer eventually funnels into one Database, that database's throughput caps the whole system's throughput no matter how much the upper layers are scaled.
Read replicas and connection pooling at the Persistence Layer buy headroom; once read and write load diverge enough, evolving toward CQRS is the more structural fix.
If you can answer these without scrolling back up, the architecture is yours. Try each one out loud before revealing.
Every great system starts as a sketch on a whiteboard. The ability to zoom out and see the whole picture (how services connect, where data flows, what breaks and why) is what separates engineers who build features from engineers who build systems. This diagram is more than boxes and arrows. It's a map of decisions, trade-offs, and intentional design.
Studying architectures isn't just about passing interviews. It's about training your intuition. The more systems you take apart, the better you get at sensing where a monolith will crack, where a queue belongs, or when a cache is hiding a deeper problem. You start seeing patterns instead of chaos.
So keep reading, keep tracing those edges, keep asking "why this way and not that way." The engineers who truly understand large systems are the ones who never stop being curious about how things fit together. That curiosity is the only ingredient that really matters.
Atharva Arbat@arbat_atharva
A server-side rate limiting system that protects APIs from abuse using sliding window counters and Redis-backed distributed state.
A ranked prefix-completion system that returns the top-K suggestions within the keystroke latency budget using an in-memory precomputed trie and an offline popularity build pipeline.
A distributed crawler that discovers, fetches, and extracts text from 10 billion web pages within 5 days while respecting robots.txt.