Section 4: Three Reads and a Refusal - API Design (with Pushback)
Executive Summary: Section 3's arithmetic said the volume is small and the semantics are hard, and the API inherits exactly that shape. This section designs the whole public contract: one Top-10 endpoint over 93 precomputed lists, one rank endpoint (plus a batch variant) over an hourly snapshot, one category best-seller endpoint, and, just as deliberately, no write endpoint at all. Then it defends the contract through five rounds of pushback: arbitrary windows, exposing raw unit counts, why every response carries asOf, why checkout doesn't call us, and how many category ranks a product page should show. The lesson underneath: an API is as much a list of things you refuse to promise as a list of things you serve.
The interviewer uncaps a marker and slides it across the table: "Okay. What do the clients actually call?"
Section 3 already told you who the callers are and how hard each one pushes: the Top-10 button (2,300/s at sale peak, 93 possible answers), product detail pages wanting a badge (116k/s at sale peak, 40M possible answers), and category best-seller pages (580/s). Three audiences, three read shapes. So you write four lines and stop:
GET /v1/top-products?scope=overall|dept:{departmentId}&window=1h|4h|24h&limit=10
GET /v1/products/{productId}/sales-rank
POST /v1/sales-ranks:batchGet (≤ 100 productIds)
GET /v1/categories/{categoryId}/best-sellers?limit=100
You: "Every one of those is a read. There's no POST /purchases, no PUT /scores. Rankings are derived data: they come from order events that already exist in the Orders DB, and I'll come back to why the ranking service never takes a write from anyone."
The Top-10 Button: An Enum, Not a Query
The first endpoint looks like a query, but it's really a lookup. scope is overall or one of the 30 departments; window is exactly 1h, 4h or 24h. That makes 31 × 3 = 93 lists, every one precomputed by the speed path each minute. Anything else, whether window=7h, window=90m or scope=dept:does-not-exist, gets a 400 that names the allowed values.
Here is Priya at 20:05, clicking "Top 10 · last 4 hours" during the evening sale:
GET /v1/top-products?scope=overall&window=4h&limit=10
200 OK
Cache-Control: public, max-age=30
{
"scope": "overall",
"window": "4h",
"asOf": "2026-10-28T20:04:00+05:30",
"items": [
{ "rank": 1, "productId": "P-10001", "title": "Nova X5 Smartphone",
"imageUrl": "https://img.clipkart.com/p/P-10001.jpg",
"price": { "amount": 24999, "currency": "INR" } },
...
]
}
Three details carry the design. First, Cache-Control: public, max-age=30. Nobody's Top-10 is personal, so all 2,300 sale-peak requests per second collapse onto 93 cache keys. With an origin shield in front of the API, the origin sees about 3 requests a second. The shield and the edge both honour the Age header, so 30 s is the end-to-end cache age, not 30 s per tier. Without one, each of 50 POPs refreshes every key on its own, and the origin sees about 155. The rejected alternative, no cache, recomputes the same 93 answers thousands of times a second. The flip trigger is personalization: once lists vary by shopper segment, the CDN caches per segment, never per user.
Second, limit is capped at 10. The pipeline keeps top-20 candidates per list because the API hydrates title, price and image from the catalog at read time and drops anything out of stock, delisted or restricted. If we allowed limit=20, some responses would come back with 17 items, and we'd be making a promise we can't keep. The extra ten candidates are a filtering buffer. They are not part of the contract.
Third, the response holds no counts. It has a rank and a product, and nothing else. Pushback 2 explains why.
On a CDN hit, the whole exchange is the first and last arrows: about 10 ms. On a miss, the origin's p99 budget is 50 ms, and that is easy to meet when the "query" is one Redis read plus a batched catalog lookup.
Quick check before moving on:
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson