Take-home: a rate-limited API

medium~240 min#api-design#rate-limiting#take-home#testing

Build a small HTTP API with the following behaviour.

Requirements

  1. POST /messages accepts a JSON body { "user_id": string, "text": string } and stores it.
  2. GET /messages?user_id=... returns that user's messages, most recent first.
  3. Each user may write at most 10 messages per minute. Over the limit, return 429.
  4. The service must survive a restart without losing stored messages.

Explicitly not specified — decide, and record the decision in your README:

  • Which language, framework and storage. Use what you are strongest in.
  • Whether the rate limit is a fixed window, sliding window, or token bucket.
  • What happens to the limit when the process restarts.
  • Pagination on the read endpoint.
  • Authentication, if any.

Time budget: four hours. Going well over is not rewarded — part of what is assessed is what you choose to leave out. If you run out of time, say in the README what you would do next and why it was not the first thing you did.

Deliver: a repository, and a README covering how to run it, how to run the tests, the decisions you made on the unspecified points, what you would do with another day, and what you would need to change before this served real traffic.

Solution

What a strong submission looks like.

The README is read before the code, and it is where most submissions are won or lost. A strong one states the unspecified decisions and gives a reason for each: "token bucket, because a user posting 5 messages at once after being idle is legitimate behaviour and a fixed window would reject it".

On the rate limiter. Any of the three algorithms is acceptable with a justification. What is not acceptable is a limiter that silently resets on restart with no acknowledgement — either persist it, or state plainly that it is in-memory and that a restart grants everyone a fresh budget, and say what you would do about it in production. Naming a known limitation is worth more than hiding it.

On testing. Not exhaustive coverage — evidence that you know what is worth testing. The rate limiter boundary (the 10th and 11th request), persistence across a restart, and the 429 response shape. A submission with three sharp tests reads better than one with forty trivial ones.

On scope. Four hours does not buy authentication, containerisation, structured logging, metrics, and a migration framework. A submission with all of those and no tests has told the reviewer what its author prioritises under time pressure, which is exactly what was being measured. Ship the requirements, test them, and use the README for everything you deliberately left out.

Common ways submissions fail: no README beyond install instructions; a rate limiter with an off-by-one at the boundary and no test that would have caught it; storing messages in a global array while claiming restart-safety; and going three days over the budget, which reads as an inability to scope rather than as diligence.