Designing an Auction System
High Level Design·intermediate·~20 min read
- system-design
- auction
- concurrency
- optimistic-locking
- real-time
- hld
What you'll learn
Problem Statement
Design a real-time auction system where users can bid on items. The system must handle concurrent bids correctly, provide real-time feedback to bidders, and ensure the highest bidder always wins. This is fundamentally a concurrency probl…
Requirements
Functional Requirements
1. Users can place bids on active auction items 2. System immediately determines if a bid is the new highest 3. Notify all active bidders on an item when the highest bid changes 4. Auctions have a fixed end time; highest bidder at end wi…
Non-Functional Requirements
1. Strong consistency — no two users should simultaneously believe they're winning 2. Real-time — bid acceptance/rejection within 500ms 3. Correctness over availability — prefer rejecting a bid over accepting an incorrect one (CP system)…
Constraints
- 100K simultaneous auctions - 10-1000 bidders per auction - ~5 bids per bidder average - 24-hour average auction duration - "Last-minute rush" traffic spike (10x in final minutes) ---
Capacity Estimation
Traffic
| Metric | Calculation | Value | |--------|-------------|-------| | Active bidders | 100K auctions × 100 avg bidders | 10M concurrent users | | Total bids/day | 100K × 100 × 5 | 50M bids | | Average bids/sec | 50M / 86,400 | ~580/sec | |…
Storage
| Data | Size per entry | Daily volume | Notes | |------|---------------|-------------|-------| | Bid record | ~100 bytes | 50M × 100B = 5 GB/day | Append-only, retained for audit | | Auction metadata | ~500 bytes | 100K auctions | Relat…
API Design
---
Design Evolution & Trade-offs
How this system grew from an MVP to what a production version looks like — and the choices you'd defend in an interview. Auctions are deceptively simple. A single row in a database, updated as bids come in. But once you scale to 100K con…
Stage 1 — MVP: One box, one database
The simplest thing that could possibly work. - A single API server (monolith) hosts auction listing, bid placement, and result pages. - A single Postgres database holds auctions, bids, and users tables. - Bids arrive via HTTP POST, are v…
Stage 2 — Scale-out: Stateless API tier + conditional writes
The first fix is twofold: make the API tier horizontally scalable, and eliminate the lost-update race with an atomic conditional UPDATE. - API layer becomes stateless; N replicas behind an L7 load balancer. - Sessions and auth move to JW…
Stage 3 — Bottleneck: Concurrent bids on a single hot auction
This is the defining problem of an auction system. Every design decision from here on either accepts it or works around it. Options for handling extreme contention on one auction row: | Option | How it works (1 line) | Latency | Cost | C…
Cost vs Complexity — When to Pick What
A rough tiering of investment vs. scale: | Concern | MVP choice | Growth choice | Enterprise choice | |---------|-----------|---------------|-------------------| | Compute | Single VM running the monolith | K8s deployment of stateless AP…
Why This Solves Concurrency
By routing all bids for the same auction through a single queue with a single consumer: - Bids are processed one at a time for each auction - No two bids for the same item are ever compared simultaneously - Race conditions are impossible…
Partition Key = auctionid
- Different auctions go to different queue partitions → processed in parallel - Same auction → same partition → processed sequentially - Horizontal scaling: more auctions = more partitions = more consumers - Single-auction throughput: li…
Trade-offs vs OCC
| Dimension | OCC (Conditional Update) | Queue Serialization | |-----------|------------------------|---------------------| | Correctness | Correct (atomic SQL) | Correct (sequential by design) | | Latency | Lower (direct DB) | Higher (q…
Response Delivery: Polling vs WebSocket
The Problem
With queue serialization, the client's HTTP request returns immediately ("bid received"). But when does the client learn if they won or were outbid?
Approach 1: Polling
Problems: - Unnecessary DB load for unprocessed bids - Latency between bid result and client awareness (polling interval) - Can't rely on Bids Service memory for status (stateless, multiple instances)
Approach 2: WebSocket (Preferred)
Advantages: 1. Zero unnecessary DB lookups 2. Instant notification when outbid (even without placing a new bid) 3. Real-time auction feel (see bids happen live) 4. Server controls when to push — only on meaningful state changes
WebSocket Scalability Concerns
- 100K auctions × ~100 active watchers = ~10M WebSocket connections - At 50K connections per server (conservative), need ~200 WebSocket servers - Connection affinity: Route all connections for one auction to same server (or use pub/sub)…
Deep Dive: Last-Minute Bid Spike
The Problem
Auctions experience 10x traffic in the final minutes ("sniping"). A popular item at 5,800 bids/sec concentrated on one auction overwhelms the single-consumer queue.
Mitigations
1. Pre-filtering (most impactful): This rejects 60-80% of bids before they enter the queue. Only potentially-winning bids get serialized. 2. Batch processing in Serializer: Instead of processing one bid at a time: This collapses N bids i…
Database Schema
Two writes per successful bid: 1. UPDATE Auctions SET currentprice, currentbidder (the serialized write) 2. INSERT INTO Bids (audit log, append-only, no concurrency concern) ---
Auction Ending
How to detect ended auctions?
Approach: Scheduled poller + TTL At scale (100K auctions): Partition the poller. Each poller instance handles auctions where hash(auctionid) % N == instanceid. ---
Full System Architecture
---
Key Interview Talking Points
1. "This is fundamentally a concurrency problem" — frame it correctly 2. OCC pattern: UPDATE WHERE price < bid — single atomic operation eliminates races 3. Queue serialization: Preferred at scale — correctness by construction 4. WebSock…