•CASE STUDY

Real-Time Online Auction (eBay / Live Auctions)

7 min read·1,336 words·Advanced

Asked at

4 candidate reports between Oct 2025 and Apr 2026

How to use this case study

SDE-2 / Mid

  • Explain the bid API
  • How the current highest bid is checked and updated safely
  • How viewers see new bids live

SDE-3 / Senior

  • Go deeper on concurrency control for bids (conditional writes or a single writer per auction)
  • The final-seconds spike
  • Closing the auction exactly on time
  • Anti-sniping

Staff / Principal

  • Discuss hot auctions with millions of watchers
  • Multi-region fairness
  • Payment after winning
  • Fraud
  • Auditability of every bid

Problem RestatementProblem

Design an online auction system. Sellers list items with a starting price and an end time. Buyers place bids, and every bid must be higher than the current highest bid. Everyone watching sees the current highest bid and the bid history update live. When time runs out, the highest valid bid wins and the winner pays.

Variants include eBay-style auctions (asked at Meta), auctions attached to a social media post (Meta, Instagram), and high-traffic live auctions (TikTok). The hard parts are many bids at once (especially in the last seconds) and closing fairly and exactly on time.

RequirementsRequirements

1.1 Functional

  • Create an auction (item, start price, minimum increment, end time).
  • Place bids, which are accepted only if higher than the current bid plus the increment.
  • See live updates of the highest bid and bid history.
  • Close the auction at the end time, pick the winner, and start payment.
  • Optional: proxy (automatic) bidding up to a max, and anti-sniping extensions.

1.2 Non-Functional

  • Correctness: never accept a lower bid over a higher one, and never lose an accepted bid.
  • Low latency: bid result in under ~200 ms, and viewers updated within ~1 second.
  • Spiky load: popular auctions get most of their bids in the final 30 seconds.
  • Auditable: every bid recorded with a timestamp.

1.3 Scale Estimates

  • 10M active auctions, 50M bids/day ≈ 600 bids/sec on average.
  • A hot auction: 5,000 bids/sec in its last seconds, and 1M watchers.
  • Bid records are small (~100 bytes), so storage is easy. Contention on one auction is the challenge.

1.4 API Design

  • POST /v1/auctions { item_id, start_price, min_increment, ends_at }
  • POST /v1/auctions/{id}/bids (Idempotency-Key) { amount } → { accepted: true, highest: 12500 } or { accepted: false, reason: "outbid", highest: 13000 }
  • WebSocket /v1/auctions/{id}/live → { highest, bidder_alias, bid_count, ends_at }

High-Level ArchitectureArchitecture

2.1 Overview

  • Auction Service: create and read auctions (catalog, search).
  • Bid Service: validates and accepts bids. All bids for one auction are processed in order by one owner (see Deep Dive A).
  • Bid store: auction state (highest bid, version) plus an append-only bid log.
  • Real-time fan-out: pub/sub channel per auction → WebSocket servers → watchers.
  • Closer: ends auctions at their end time and triggers winner payment.
  • Payment Service: charges the winner, with a fallback to the next bidder if payment fails.

2.2 Architecture Diagram

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
    B["Bidders"] --> GW["API Gateway"]
    GW --> BS["Bid Service - owner per auction"]
    BS --> ST[("Auction state + bid log")]
    BS --> PS[("Pub/Sub - channel per auction")]
    PS --> WS["WebSocket servers"]
    WS --> W["Watchers"]
    CL["Auction Closer - timer"] --> ST
    CL --> PAY["Payment Service"]
    PAY --> N["Notify winner / seller"]

Data ModelData model

auctions:  auction_id, seller_id, item_id, start_price, min_increment, starts_at, ends_at,
           status (scheduled, live, closing, closed), highest_amount, highest_bidder_id, version
bids:      auction_id, bid_id, bidder_id, amount, server_ts, status (accepted/rejected)   -- append-only

Key FlowsFlows

4.1 Placing a bid

  1. Check that the bidder is verified (has a payment method), the auction is live, and it's not the bidder's own auction.
  2. Atomically: accept only if amount >= highest_amount + min_increment and the auction hasn't ended. Update highest_amount, highest_bidder_id and version, and append the bid to the log.
  3. Publish { highest, bid_count } to the auction's channel. Watchers see it within a second.
  4. Notify the previous highest bidder that they were outbid.

4.2 Closing

  1. The closer has a timer per auction (a delayed queue or sorted set by ends_at).
  2. At ends_at, it sets status = closing. From now on, the atomic bid check rejects new bids, because it checks both status and time.
  3. It reads the final highest bid, sets closed, records the winner and starts payment.

Deep Dive A — Thousands of bids on one auctionDeep dive

Every bid on a hot auction wants to read and then raise the same number. This is the classic contention problem, and the usual answer is not the best one.

Weak

Read the highest bid, compare, write

Fetch highest, check that the new bid beats it, write the new value and append to the bid history.

Sequence diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
sequenceDiagram
  participant A as Bidder A - 510
  participant B as Bidder B - 505
  participant DB as Auction row
  A->>DB: read highest = 500
  B->>DB: read highest = 500
  A->>DB: write highest = 510
  B->>DB: write highest = 505
  Note over DB: the 510 bid is gone, 505 is winning

Both bids passed a check against the same stale number. The higher bid vanishes, the bid history disagrees with the winning price, and the seller is owed money nobody can account for.

Good

Conditional update with a version

Make the check part of the write: UPDATE auctions SET highest = ?, version = version + 1 WHERE id = ? AND version = ? AND highest < ?. Zero rows updated means someone got there first, so re-read and retry.

This is correct, and for most auctions it is the answer. Under real contention it degrades badly: at a thousand bids a second on one row, nearly every request loses its race and retries, and the retries collide with each other. Throughput falls as load rises, which is the worst possible shape, and the retry loop makes latency unpredictable exactly during the closing seconds.

Best

One writer per auction

Route every bid for an auction to a single consumer — a Kafka partition keyed by auction_id, or an in-memory actor pinned to one server. It handles bids one at a time in arrival order.

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
  B1["Bid"] --> P["Partition by auction_id"]
  B2["Bid"] --> P
  B3["Bid"] --> P
  P --> W["Single writer - auction 77"]
  W --> MEM["Highest bid in memory - no locks, no retries"]
  MEM --> LOG[("Append to the durable bid log")]
  LOG --> ACK["Reply to the bidder"]
  LOG --> PUB["Publish the new highest to watchers"]

There is no contention left to manage, because there is no concurrency on that auction — comparisons happen in a loop on one core, and one partition handles thousands of them per second without breaking a sweat. Different auctions sit on different partitions, so the system as a whole scales out normally.

Two rules that come with it:

  • Write the bid to the durable log before replying. The in-memory state is a cache of the log; if the writer dies, the next one replays and continues.
  • Arrival order at the server decides fairness. Two equal bids: the first one received wins. Client timestamps play no part — they cannot be trusted and they are not needed.

Deep Dive B — The final secondsDeep dive

  • Anti-sniping: if a bid arrives in the last 30 seconds, extend the end time by 30 seconds. This removes the incentive to bid at the last millisecond and reduces spikes.
  • Clock authority: only the server's clock decides whether a bid was on time. Clients show a countdown but never decide.
  • Fan-out at 1M watchers: two-level pub/sub (as in live comments). Updates are coalesced: send the latest highest bid at most every 200–500 ms rather than every bid.
  • Payment failure: if the winner's payment fails, offer the item to the second-highest bidder (at their bid), and flag the non-paying winner.

Trade-offs & AlternativesTrade-offs

DecisionChoiceWhyAlternative
Bid orderingSingle writer per auctionNo lost updates, no retries under contentionOptimistic locking: fine for quiet auctions
DurabilityAppend bid log before replyAudit trail, recoverable stateUpdate only the current max: no history
Live updatesPub/sub + coalesced pushesScales to 1M watchersPush every bid to everyone: floods clients
End of auctionServer-side closer + anti-snipingFair, predictableHard stop: sniping wars and spikes

Common Follow-up QuestionsFollow-ups

  • "Proxy bidding?" Store each bidder's secret max. On a new bid, the system automatically raises the leader's visible bid to just above the challenger, up to their max. The single writer makes this easy.
  • "What if the bid owner server crashes?" A new owner rebuilds the state from the durable bid log before accepting new bids.
  • "Search and browse?" A separate search index of live auctions, updated from auction events, sorted by ending soon or price.

Wrap-UpWrap-up

Process all bids for an auction through one ordered owner (or optimistic conditional writes when traffic is low), accept a bid only if it beats the current highest by the increment and the auction is still open by server time, and append every bid to a durable log. Push coalesced updates through per-auction pub/sub to WebSocket watchers, close auctions with a server-side timer plus anti-sniping extensions, and fall back to the next bidder if payment fails.

More Case Studies

Frequently Asked Questions

What is the Real-Time Online Auction (eBay / Live Auctions) system design question?

Real-Time Online Auction (eBay / Live Auctions) is a system design interview question asked at FAANG companies. It covers real-time, distributed systems, payments, messaging and tests your ability to design scalable, production-ready systems. InterviewSkool's breakdown walks you through requirements, API design, architecture, and trade-offs.

Which companies ask the Real-Time Online Auction (eBay / Live Auctions) question?

Meta, TikTok have reportedly asked variations of this question in system design interviews. The exact wording may differ, but the core design challenges remain the same.

How should I prepare for the Real-Time Online Auction (eBay / Live Auctions) interview question?

Start with the problem statement and scale estimates, then design the high-level architecture. Focus on the core components, data model, and API design. InterviewSkool's breakdown covers the full solution with mermaid diagrams and trade-off analysis to help you prep efficiently.

What level is the Real-Time Online Auction (eBay / Live Auctions) question?

This question is suitable for SDE-2, SDE-3, and Staff engineer interviews. The level guidance on this page provides specific tips for each level — SDE-2 candidates should focus on core architecture, while Staff engineers should discuss trade-offs, monitoring, and incremental rollouts.

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with InterviewSkool's AI interviewer.

Start System Design Interview →