Rate Limiting in Spring Boot: The Interview Question That Tests More Than You Think
A backend interview question I recently came across looked deceptively simple:
“Build a Spring Boot application with CRUD operations and implement an in-memory rate limiter. If a user exceeds 10 requests per minute on any endpoint, return HTTP 429 (Too Many Requests).”
At first glance, this sounds like a straightforward coding exercise.
In reality, it evaluates your understanding of backend architecture, concurrency, HTTP semantics, and clean design.
Let’s break down what the interviewer is actually testing.
What Is Rate Limiting?
Rate limiting is a mechanism that restricts how many requests a client can make within a given period.
For example:
Limit:
10 requests / minuteRequest #11
↓HTTP 429 Too Many Requests
Without rate limiting, a single client could overwhelm your application by sending thousands of requests, affecting every other user.
Rate limiting protects:
- APIs from abuse
- Server resources
- Downstream services
- Overall application stability
What This Interview Question Is Really Testing
Although the requirement is small, it touches multiple backend concepts.
1. REST API Fundamentals
Can you build a clean Spring Boot CRUD application?
This verifies that you’re comfortable with:
- Controllers
- Services
- Repositories
- DTOs
- REST conventions
2. HTTP Status Codes
Many developers only think about:
- 200 OK
- 400 Bad Request
- 404 Not Found
- 500 Internal Server Error
A production backend should also know when to return:
429 Too Many RequestsThis status tells the client:
“Your request is valid, but you’ve exceeded the allowed request rate.”
3. Algorithm Selection
There isn’t just one rate-limiting algorithm.
Common approaches include:
Fixed Window
Simple counter per time window.
Pros:
- Easy to implement
Cons:
- Burst traffic at window boundaries
Sliding Window
Stores request timestamps.
On every request:
- Remove timestamps older than the window.
- Count remaining requests.
- Reject if limit exceeded.
Pros:
- More accurate
Cons:
- Slightly more memory usage
Token Bucket
A bucket contains tokens.
Each request consumes one token.
Tokens refill gradually over time.
Pros:
- Allows controlled bursts
- Widely used in production
- Used internally by libraries such as Resilience4J
Cons:
- Slightly more complex
Why Thread Safety Matters
Imagine two requests arrive simultaneously.
Both read:
Current Count = 9Both increment:
10You just allowed 11 requests.
This is called a race condition.
Using a regular HashMap is unsafe because multiple threads may modify it simultaneously.
Instead, use:
ConcurrentHashMapwhich provides thread-safe operations for concurrent access.
Why the Rate Limiter Should Not Be Inside the Controller
A common beginner implementation looks like this:
@PostMapping("/users")
public ResponseEntity<?> createUser() {if(rateExceeded()){
return ResponseEntity.status(429).build();
} ...
}
It works.
Get curiouswomen’s stories in your inbox
Join Medium for free to get updates from this writer.
But now every controller contains duplicated rate-limiting logic.
Tomorrow you’ll need to copy the same code into:
- UserController
- ExpenseController
- GroupController
- BalanceController
This violates the DRY (Don’t Repeat Yourself) principle.
The Better Design
Rate limiting is a cross-cutting concern.
It should be handled before the request reaches the controller.
In Spring Boot, the request flow looks like this:
Incoming Request
│
▼
Servlet Filter
│
▼
DispatcherServlet
│
▼
HandlerInterceptor
│
▼
Controller
│
▼
ServiceA custom Filter (or OncePerRequestFilter) can intercept every request and decide whether it should proceed.
This keeps controllers focused solely on business logic.
A Simple In-Memory Design
One possible approach is:
ConcurrentHashMap<
ClientIdentifier,
Queue<RequestTimestamp>
>Where:
- Key = client (IP, API key, or user)
- Value = queue of request timestamps
For every incoming request:
- Remove timestamps older than 60 seconds.
- Check queue size.
- If size >= 10 → return HTTP 429.
- Otherwise add the current timestamp and continue.
This is essentially a Sliding Window implementation.
Why This Design Is Good
- Thread-safe
- Centralized
- Easy to extend
- No duplicated controller logic
- Easy to replace later with Redis or Resilience4J
In-Memory vs Production
An in-memory implementation is excellent for interviews and learning.
However, in production it has limitations.
Problems
- Doesn’t work across multiple application instances.
- Loses data after restart.
- Doesn’t scale horizontally.
Production systems usually use:
- Redis
- API Gateway rate limiting
- Cloud load balancers
- Resilience4J (for outbound calls)
An Important Distinction
Many developers confuse two different concepts.
API Rate Limiting
Protects your application.
Client
↓Spring Boot API
↓HTTP 429
Resilience4J RateLimiter
Protects external dependencies.
Example:
Spring Boot↓Google Maps API
Suppose Google allows only 5 requests per second.
Resilience4J prevents your application from exceeding that limit.
Although both are called “Rate Limiting,” they solve different problems.
What the Interviewer Is Actually Looking For
The best solution is not the shortest one.
A strong answer demonstrates:
- Clean REST architecture
- Correct HTTP semantics
- Thread-safe programming
- Separation of concerns
- Understanding of request lifecycle
- Awareness of production trade-offs
In other words, one seemingly simple question evaluates far more than just coding ability.
Final Thoughts
Whenever you encounter a backend interview question, don’t rush into writing code.
First ask yourself:
- Where does this feature belong?
- Is it a business concern or a cross-cutting concern?
- Will this design still work if my application scales tomorrow?
That shift — from simply solving the problem to designing the solution — is what differentiates a backend developer from a backend engineer.





