</> MAANG.io
system design · 301

Advanced

Master system design interviews with scalable architecture patterns, distributed systems, and real-world design challenges.

0/480 solved 0% complete

Section 4: The Answer You Get Back Immediately - API Design

Executive Summary: Section 3 showed that bid writes peak at a modest 5,000 per second, so this contract was never about throughput. Its job is to tell a bidder the truth in one round trip. Five endpoints carry the product: create an auction, place a bid, read state, read history, and subscribe to a stream, plus a watch toggle. The one that matters is POST /auctions/{id}/bids: it accepts a maximum in minor units, is guarded by an Idempotency-Key, optionally carries the price the bidder was looking at, and returns a verdict of HIGH_BIDDER, OUTBID or REJECTED synchronously. Five pushbacks test each of those choices, an error table pins down what every failure means, and additive versioning keeps the oldest clients in the world, sniping tools, working.

The interviewer puts the marker down. "Show me the API." The reflex answer is a resource list: auctions, bids, watchers, users. Resist it, because one endpoint carries everything Section 1 established. A bidder does not submit a price. A bidder submits the most they are willing to pay, and the system owes them an immediate, final answer about where that leaves them.

Five Endpoints, and What Each One Refuses to Say

Endpoint Who Returns Deliberately absent
POST /auctions Seller auction_id, end_time, status: SCHEDULED for a future start_time, else OPEN Listing content, images, shipping. The Listing service owns those
POST /auctions/{id}/bids Bidder A verdict, synchronously Anyone else's maximum
GET /auctions/{id} Anyone current_price, high bidder alias, bid_count, end_time, version, server_time (countdowns must run on the server's clock, Section 8's rule) high_max_amount, always
GET /auctions/{id}/bids Anyone History: price_after and a masked alias per accepted bid Maxes, bidder identities
GET /auctions/{id}/stream Viewers SSE or WebSocket state updates, coalesced to at most 2 per second The bidder's own verdict, which travels on the response
PUT / DELETE /auctions/{id}/watch Signed-in user 204 Nothing interesting, which is the point

Here is Elena's Thursday bid on #7731. The page showed $4,350; she decides she will go to $9,500.

POST /auctions/7731/bids
Idempotency-Key: bid_7f3a9c
{ "max_amount": 950000, "currency": "USD", "expected_price": 435000 }

200 OK
{ "outcome": "OUTBID",
  "current_price": 955000, "high_bidder_is_you": false,
  "bid_count": 61, "end_time": "2026-10-25T19:00:00.000-07:00",
  "version": 61 }

Three details are load-bearing. Amounts are integers in minor units (950000 is $9,500.00): no float ever touches money, and an eBay-style increment table that steps in cents stays exact. The Idempotency-Key is minted by the client when the intent forms, as the Confirm button renders, and reused for every retry of that attempt. And the response is a verdict, not a receipt: Elena learns in the same round trip that a standing maximum beat her and that the price is now $9,550 because of her. What she never learns is that Marcus's maximum is $12,000.

Sequence of Elena's bid on auction 7731 through API Gateway, Bid Service and Auction Store, then a retry with the same Idempotency-Key that returns the identical stored OUTBID verdict without a second bid

The second tap never reaches the auction row. It also never meets the rate limiter, because the replay check runs first: a retry is a retry, not a second bid, and punishing it with a 429 would teach clients to invent fresh keys.

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

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.