Problem Overview
A live auction system allows bidders to compete in real time, with each bid potentially triggering updates for hundreds of concurrent viewers. The core difficulty is handling a burst of bids at the moment a high-value item is closing, enforcing strict ordering of bids, and preventing fraudulent double-bids — all while pushing state updates to clients with sub-second latency.
Requirements and Constraints
Functional Requirements
- Bidders submit bids on active auction items
- Each valid bid becomes the new “current price” if it exceeds the previous high bid
- All connected clients receive real-time updates when the price changes
- Auctions have a hard end time; anti-sniping extends the auction if a bid arrives in the last N seconds
- Reserve price logic: seller sets a hidden minimum; auction only completes if reserve is met
- Bid history is fully auditable
Non-Functional Requirements
- Bid acceptance latency under 100 ms p99
- Support 10,000 concurrent viewers per auction item
- Handle 500 bids/second per item during closing surge
- Exactly-once bid processing (no duplicate bid credits)
- Zero tolerance for accepting a bid below the current high bid
Core Data Model
CREATE TABLE auctions (
auction_id BIGINT PRIMARY KEY AUTO_INCREMENT,
item_id BIGINT NOT NULL,
seller_id BIGINT NOT NULL,
start_price DECIMAL(12,2) NOT NULL,
reserve_price DECIMAL(12,2), -- hidden from bidders
current_price DECIMAL(12,2) NOT NULL,
current_winner BIGINT,
end_time TIMESTAMP NOT NULL,
status ENUM('active','closed','cancelled') DEFAULT 'active',
anti_snipe_sec INT NOT NULL DEFAULT 60,
version BIGINT NOT NULL DEFAULT 0 -- optimistic lock
);
CREATE TABLE bids (
bid_id BIGINT PRIMARY KEY AUTO_INCREMENT,
auction_id BIGINT NOT NULL,
bidder_id BIGINT NOT NULL,
amount DECIMAL(12,2) NOT NULL,
placed_at TIMESTAMP(3) NOT NULL DEFAULT NOW(3),
status ENUM('accepted','rejected','cancelled') NOT NULL,
idempotency_key VARCHAR(64) UNIQUE NOT NULL,
INDEX idx_auction_amount (auction_id, amount DESC)
);
Key Algorithms and Logic
Bid Processing with Optimistic Concurrency Control
The bid acceptance path uses optimistic locking on the auctions table to prevent lost updates without holding a long lock:
- Read the current auction row: current_price, version, end_time, status.
- Validate: auction is active, end_time is in the future, bid.amount > current_price, bidder has sufficient funds on hold.
- Execute:
UPDATE auctions SET current_price=?, current_winner=?, version=version+1, end_time=CASE WHEN end_time - NOW() < anti_snipe_sec THEN end_time + INTERVAL anti_snipe_sec SECOND ELSE end_time END WHERE auction_id=? AND version=? - If 0 rows affected: a concurrent bid won the race. Re-read and retry up to 3 times; if still failing, reject with “outbid.”
- Insert the bid record with idempotency_key to prevent duplicate submissions.
This avoids SELECT FOR UPDATE while still preventing double-acceptance. Under very high contention, switch the hot row to a Redis-based compare-and-swap using WATCH / MULTI / EXEC on a Redis hash, and flush to the DB asynchronously via a dedicated writer thread.
Anti-Sniping
When a bid arrives within anti_snipe_sec of the auction's end_time, the UPDATE extends end_time by that interval. This is atomic with the bid acceptance in a single UPDATE statement, so there is no race between checking the time and extending it. The extension is capped at a maximum total extension (e.g., 30 minutes) to prevent infinite auctions.
Reserve Price Logic
The reserve_price column is never exposed via the API. At auction close, the system compares current_price against reserve_price. If current_price < reserve_price, the auction status is set to “reserve_not_met” and no transaction is initiated. Bidders may see a UI indicator “Reserve not met” without knowing the reserve value.
Real-Time Update Architecture
Clients connect via WebSocket. A bid acceptance service publishes a bid_accepted event to a Redis Pub/Sub channel keyed by auction_id. A WebSocket gateway service subscribes to the channel and fans out the event to all connected clients for that auction. The gateway is stateless; sticky sessions (via load balancer cookie) are not required because subscriptions are per-auction, not per-server.
For 10,000 concurrent viewers on a single item, each WebSocket gateway node can handle ~5,000 connections. Two gateway nodes per popular auction provide redundancy. The fanout is O(subscribers) per bid event — acceptable because bid events are rare compared to connection counts.
API Design
POST /v1/auctions/{auction_id}/bids
Body: { "bidder_id": "...", "amount": 1250.00, "idempotency_key": "uuid" }
Response 200: { "bid_id": 9821, "status": "accepted", "new_price": 1250.00, "end_time": "..." }
Response 409: { "status": "rejected", "reason": "outbid", "current_price": 1275.00 }
GET /v1/auctions/{auction_id}
Response: { "auction_id": ..., "current_price": 1250.00, "end_time": "...", "bid_count": 47 }
WebSocket: wss://live.example.com/auctions/{auction_id}
Server push: { "event": "bid_accepted", "new_price": 1250.00, "end_time": "...", "bid_count": 47 }
Scalability Considerations
- Read fan-out: Auction detail reads are served from a Redis cache invalidated on each bid_accepted event. This offloads the DB entirely for viewers.
- Write path isolation: Route all writes for a single auction_id to the same DB shard (auction_id % N shards) to avoid cross-shard transactions.
- Fund hold service: Pre-authorize bidder funds before allowing bidding. Use a separate balance service with a reserved_amount field updated atomically. This prevents bid acceptance for bidders without sufficient balance without coupling to payment processing on the hot path.
Failure Modes and Mitigations
- Duplicate bid submission: idempotency_key unique constraint causes the second INSERT to fail; the service returns the original bid result.
- Gateway crash mid-auction: Clients reconnect; on reconnect, the server sends the current auction state immediately. No bids are lost because bids go through the bid service, not the gateway.
- Clock skew: end_time comparisons happen on the DB server using
NOW()inside the UPDATE, not on the application server, ensuring consistency regardless of application clock drift.
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering
See also: Shopify Interview Guide
See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems