Interview question
Apply a partitioned write rate limit
Partitions limits by authenticated user or trusted client address and returns useful 429 retry metadata.
TL;DR
Partitions limits by authenticated user or trusted client address and returns useful 429 retry metadata.
ASP.NET Core rate limiting, partition keys, token buckets, forwarded headers, 429 behavior, and observability.
Practice the problem like a real interview: restate, reason, implement, and test.
Allow a short burst of writes per authenticated user and a stricter anonymous limit per client address. Return 429 and Retry-After when rejected.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Practice the next layer of the same subject.
One abusive caller exhausts only its own partition; other authenticated users continue normally.
I prefer an authenticated subject for the partition and fall back to the resolved client address. Token-bucket replenishment allows a small burst while preserving a sustained rate.
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.OnRejected = async (context, ct) =>
{
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retry))
context.HttpContext.Response.Headers.RetryAfter =
Math.Ceiling(retry.TotalSeconds).ToString(CultureInfo.InvariantCulture);
await context.HttpContext.Response.WriteAsJsonAsync(new ProblemDetails {
Title = "Too many requests",
Status = StatusCodes.Status429TooManyRequests
}, ct);
};
options.AddPolicy("expensive-write", http =>
{
var userId = http.User.FindFirstValue(ClaimTypes.NameIdentifier);
var authenticated = userId is not null;
var subject = authenticated
? $"user:{userId}"
: $"ip:{http.Connection.RemoteIpAddress ?? IPAddress.None}";
return RateLimitPartition.GetTokenBucketLimiter(subject, _ =>
new TokenBucketRateLimiterOptions {
TokenLimit = authenticated ? 10 : 3,
TokensPerPeriod = authenticated ? 5 : 1,
ReplenishmentPeriod = TimeSpan.FromMinutes(1),
QueueLimit = 0,
AutoReplenishment = true
});
});
});
// ForwardedHeadersOptions must allow only known proxy networks or addresses.
app.UseForwardedHeaders();
app.UseRouting();
app.UseAuthentication();
app.UseRateLimiter();
app.UseAuthorization();
app.MapPost("/exports", CreateExport)
.RequireRateLimiting("expensive-write");
In-memory partitions apply per app instance. A scaled public API may need gateway or distributed enforcement, but endpoint policy and 429 behavior should remain predictable.
Spot a weak answer, missing edge case, or clearer explanation? Send it in.