Question Breakdowns
Online Auction
A worked system design: run live auctions with concurrent bids, a strictly-increasing price, real-time updates to watchers, and a reliable close. Contention on the current bid and the ending race.
An online auction lets many people bid on an item, shows the current high bid live, and declares a winner when time runs out. It's a compact but sharp design because it combines two hard things: contention on a single hot value (the current highest bid, which many bidders race to beat) and a correct, timely close (deciding the winner exactly at the deadline, fairly). It draws on Dealing with Contention and Real-time Updates.
1. Requirements
Functional:
- Place a bid on an item; a bid is valid only if it exceeds the current high bid (by a minimum increment).
- Watchers see the current high bid update live.
- At the end time, the highest bid wins; no bids accepted after.
Non-functional:
- Correct under concurrency — the price must be strictly increasing; no two bids "both win."
- Real-time — watchers see new highs within moments.
- Fair close — the deadline is authoritative; late bids are rejected cleanly.
- Durable — bids and the outcome must never be lost (money/goods at stake).
2. The contention: beating the high bid
The hot spot is the item's current highest bid — on a popular item, many bidders try to beat it at once. A bid is valid only if it's higher than the current max, so placing a bid is a read-check-write that must be atomic, or two bidders reading the same "current high" could both think they won. This is textbook contention:
- Conditional update (optimistic):
UPDATE item SET high_bid=?, high_bidder=? WHERE id=? AND high_bid < ?. If it updates 0 rows, someone already bid higher — reject and tell the client the new price. The database's atomicity guarantees exactly one winner per round. - Per-item serialization: route all bids for one item through a single owner/lock so they're applied in order.
Append every bid attempt to a durable bid log (even losers) for auditability; the item row holds the current winner.
bid → atomic: if bid > current_high then set current_high = bid else reject
→ append to bid history (durable)
→ broadcast new high to watchers3. Real-time updates to watchers
Everyone watching an item needs the new high bid immediately. Push it over a real-time channel (WebSocket/SSE): when a bid wins, publish the new high to all watchers of that item (a Redis pub/sub channel per item fans it out across servers). Watchers vastly outnumber bidders, so this is a fan-out-to-many broadcast.
4. The close: the ending race
Deciding the winner at the deadline is subtler than it looks:
- The deadline is server-authoritative. Reject any bid whose server-receipt time is past the end — never trust client clocks. A bid arriving a millisecond late is cleanly rejected, not argued over.
- Closing the auction is a scheduled action: at end time, mark the item closed (no more bids accepted) and finalize the current high bidder as the winner. A job scheduler or a durable timer (e.g. Temporal) fires the close reliably even across restarts.
- Boundary correctness: a bid in flight at the deadline must be resolved deterministically — compare its server timestamp to the end; the atomic update either lands before close or is rejected after.
5. Deep dives
- Anti-snipe extension: some auctions extend the end by a few minutes if a bid lands in the final seconds — just a rule applied at bid time that pushes the scheduled close.
- Hot item scaling: a blockbuster item funnels all bids through one row/owner; that single-item throughput is the ceiling. Keep the critical section tiny (one conditional update) so it's fast; watchers scale separately via pub/sub.
- Payment on win: finalizing hands off to a payment flow; hold/charge the winner, with compensation if it fails.
- Proxy/auto bids: "bid up to X automatically" is resolved server-side against incoming bids.
What to emphasise
Name the contention on the high bid and the authoritative close:
"The hot spot is the item's current high bid, which many bidders race to beat, so placing a bid is an atomic conditional update — set the high bid only if mine exceeds the current one — which guarantees exactly one winner per round and a strictly increasing price; losers are rejected with the new price and every attempt is logged durably. New highs broadcast to watchers via per-item pub/sub over WebSockets, and watchers scale independently of bidders. The close is server-authoritative: the deadline is judged by server receipt time, and a durable scheduled timer finalizes the winner reliably even across restarts, with anti-snipe handled as a rule that extends the scheduled close."
Next up: Job Scheduler — running tasks reliably at a future time.
