Auction system
Low-level design of an online auction system covering item listing, bid placement, auction lifecycle, anti-sniping extensions, proxy/auto-bidding, winner determination, and real-time bid notifications.
The Problem
Your company runs an online marketplace where sellers list items for auction. The current system is a single-threaded loop that accepts bids in order of arrival. Last week, two bidders submitted bids within milliseconds of each other on a vintage Rolex. Both saw the same "current highest bid" of $4,200, and both placed $4,300 bids. The system accepted both, charged both buyers, and your support team spent three days untangling the mess.
Auction systems are harder than simple e-commerce because bids are competitive and time-sensitive. The system must enforce bid ordering under concurrency, support different auction formats (English ascending, Dutch descending, sealed-bid), handle sniping (last-second bids), and notify bidders in real time when they are outbid. The lifecycle of an auction is a state machine with strict transition rules.
Design the core classes for an online auction system that handles item listing, bid placement with concurrency control, auction lifecycle management, multiple auction types, proxy bidding, anti-sniping time extensions, winner determination, and real-time bid notifications.
Requirements
Clarifying Questions
Before jumping into class design, ask questions to narrow the problem. Cover four areas: core actions, error handling, boundaries, and future extensions.
You: "What types of auctions does the system support? Just English ascending, or others too?"
Interviewer: "Support four types: English (ascending bids), Dutch (descending price), sealed-bid first-price, and Vickrey (sealed-bid second-price). The system should make it easy to add new auction types later."
Four distinct auction types with different winner-determination logic. That is a clear Strategy pattern signal. Each auction type calculates the winning bid differently.
You: "How does bidding work? Does a bidder specify an exact amount, or can they set a maximum and let the system bid on their behalf?"
Interviewer: "Both. Manual bids specify an exact amount. Proxy bids let the user set a secret maximum, and the system automatically places the minimum necessary bid to keep them in the lead."
Proxy bidding adds a layer of automated bid resolution. The system must compare proxy maximums and bid just enough to beat the second-highest proxy. This is a core differentiator from simple bid placement.
You: "What happens when someone snipes, placing a bid in the last few seconds to prevent counter-bids?"
Interviewer: "The system supports anti-sniping. If a bid arrives in the last N minutes of an auction, the end time extends by N minutes. The extension window is configurable per auction."
Anti-sniping means the auction end time is not fixed. Every late bid shifts the deadline. The placeBid method must check remaining time and extend if needed.
You: "Does the auction have a reserve price? What happens if no bid meets it?"
Interviewer: "Yes, sellers can set an optional hidden reserve price. If the highest bid is below the reserve when the auction ends, no winner is declared."
Reserve price adds a condition to winner determination. The auction can end with bids but still have no winner.
You: "Is there a minimum bid increment? Can someone outbid by one cent?"
Interviewer: "Yes, each auction has a configurable minimum increment. A bid must exceed the current highest by at least that amount."
Minimum increment validation is part of bid placement. Reject any bid that does not meet the threshold.
You: "What notifications does the system send? Who gets notified and when?"
Interviewer: "Notify the outbid bidder immediately when someone places a higher bid. Notify all watchers when the auction is about to end. Notify the winner and seller when the auction closes."
Three notification events: outbid, ending soon, and auction closed. That is an Observer pattern with multiple event types and subscriber lists.
You: "Should the system support multiple items per auction, like a lot?"
Interviewer: "No, one item per auction for the initial design. Multi-item lots are a good extensibility discussion."
Good. Single item keeps the model clean.
You: "Can sellers cancel an active auction? Under what conditions?"
Interviewer: "Only if no bids have been placed yet. Once there is at least one bid, the auction must run to completion."
Cancellation is conditional. The state machine transition from ACTIVE to CANCELLED is only valid when the bid count is zero.
Final Requirements
Functional Requirements:
- Sellers create auctions with an item, starting price, optional reserve price, bid increment, auction type, and start/end times
- Bidders place manual bids that must exceed the current highest bid by at least the minimum increment
- Bidders configure proxy bids with a secret maximum; the system auto-bids the minimum necessary amount on their behalf
- Auctions move through a defined lifecycle: DRAFT, ACTIVE, CLOSING, ENDED, CANCELLED
- Anti-sniping extends the auction end time when a bid arrives within the last N minutes (configurable)
- Winner determination follows the rules of the selected auction type (English, Dutch, sealed-bid first-price, Vickrey second-price)
- The system notifies bidders in real time when they are outbid, when the auction is ending, and when it closes
Non-Functional Requirements:
- Concurrent bids on the same auction must be handled safely (synchronized compare-and-set)
- Adding new auction types requires a new class, not changes to existing code
- Adding new notification channels (email, SMS, push) requires a new observer, not changes to bid logic
Out of Scope:
- Payment processing (use an interface)
- Shipping and fulfillment
- User authentication and authorization
- Persistence / database layer
- UI rendering
- Multi-item lot auctions (extensibility discussion only)
Interview tip
In a real interview, these requirements come from YOUR questions to the interviewer. Writing them on the whiteboard shows you are methodical and organized.
Example Inputs and Outputs
Scenario 1: Successful English auction with manual bids
- Input: Seller creates auction for "Vintage Rolex Submariner", starting price $1,000, reserve $3,000, increment $100, English type, ends in 24 hours
- Bidder A bids $1,000, Bidder B bids $1,200, Bidder A bids $1,500, Bidder C bids $3,500
- Expected: Auction ends. Highest bid ($3,500) exceeds reserve ($3,000). Winner: Bidder C at $3,500. Seller and winner notified.
Scenario 2: Anti-sniping extension
- Input: Auction ends at 3:00 PM with 5-minute anti-snipe window. At 2:57 PM, Bidder D places a $4,000 bid.
- Expected: End time extends to 3:02 PM (5 minutes from the bid timestamp). Other bidders now have time to counter-bid. If another bid arrives at 3:01 PM, the end extends again to 3:06 PM.
Scenario 3: Proxy bidding duel
- Input: Bidder A sets proxy max at $2,000 with $100 increment. Bidder B sets proxy max at $1,800. Current price is $1,000.
- Expected: System auto-bids: B bids $1,100, A auto-bids $1,200, B auto-bids $1,300, A auto-bids $1,400, ... until B reaches $1,800. A wins at $1,900 (one increment above B's max). B is notified of outbid.
Scenario 4: Reserve not met
- Input: Auction with $5,000 reserve. Highest bid at close is $4,500.
- Expected: Auction ends with status ENDED, no winner declared. Seller notified that reserve was not met. All bidders notified that auction closed without a sale.
Try It Yourself
Try it yourself
Before reading the solution, spend 20 minutes sketching your class diagram. Focus on three challenges: how to model different auction types without if-else chains, how to make bid placement thread-safe, and where the anti-sniping logic lives. Compare your approach with the walkthrough below.
Step 1: Identify Core Entities
Start by pulling nouns from your requirements. Every requirement sentence hides at least one entity. I underline the nouns and ask: "Does this thing have its own lifecycle, state, or responsibility?"
Auction systems have more entities than most LLD problems because the bidding process touches inventory, timing, notification, and strategy selection. A common mistake is making a single Auction god class that does everything. Good design means each class owns one job.
| Entity | Responsibility | Key attributes |
|---|---|---|
| Auction | The orchestrator. Owns lifecycle state, timing, and bid acceptance rules. | auctionId, item, startingPrice, reservePrice, increment, startTime, endTime, state |
| AuctionItem | The thing being sold. Pure data holder with description and photos. | itemId, title, description, sellerId |
| Bid | A single bid placed by a user. Immutable once created. | bidId, auctionId, bidderId, amount, timestamp, isProxy |
| User | A person who can be a seller or bidder (or both). | userId, username, email |
| ProxyBid | A bidder's secret maximum. The system bids on their behalf up to this ceiling. | userId, auctionId, maxAmount |
| AuctionResult | The outcome when an auction ends. Records winner (if any), final price, and result type. | auctionId, winnerId, winningAmount, resultType |
| BidHistory | Ordered log of all bids on an auction. Supports audit trail and bid replay. | auctionId, bids (ordered list) |
| AuctionType | Strategy interface. Each implementation defines how winners are determined. | determineWinner(), validateBid() |
| AuctionState | State interface. Each implementation defines valid transitions and allowed actions. | placeBid(), close(), cancel() |
Notice that Auction and AuctionItem are separate. The Auction is the event (the sale process with timing and rules). The AuctionItem is the thing being sold (with a title and description). An item can be re-listed in a new auction if the first one fails. Merging them means you cannot re-auction an unsold item without duplicating data.
Step 2: Define Relationships and Class Design
Part 1: Class Diagram
Part 2: Deriving State and Methods
Auction (the orchestrator)
The Auction is the central class. It owns the lifecycle, connects the item to bidders, coordinates with the type strategy for winner determination, and delegates state transitions to the state pattern. Every action in the system either creates, modifies, or queries an Auction.
Deriving state from requirements:
| Requirement | What Auction must track |
|---|---|
| "Auctions move through DRAFT, ACTIVE, CLOSING, ENDED, CANCELLED" | Current lifecycle state |
| "Sellers create auctions with starting price, reserve, increment" | Pricing configuration |
| "Anti-sniping extends end time" | End time (mutable) and snipe window config |
| "Multiple auction types" | The type strategy for this auction |
| "Real-time notifications" | List of event listeners |
| "Proxy bidding" | Map of user proxy bid configurations |
Deriving methods from needs:
| Need from requirements | Method |
|---|---|
| "Bidders place bids" | placeBid(User, Money) |
| "Auction goes live at start time" | activate() |
| "Auction ends and determines winner" | close() |
| "Seller cancels if no bids" | cancel() |
| "Anti-sniping extends deadline" | extendEndTime(int minutes) |
| "Observers subscribe to events" | addListener(AuctionEventListener) |
The state machine is the most important design decision. Invalid transitions (DRAFT to ENDED, CANCELLED to ACTIVE) must be rejected. Each state object defines which actions are allowed and which trigger transitions.
BidHistory (the bid ledger)
BidHistory is the ordered record of every bid on an auction. It exists so that winner determination strategies can query the full bid sequence without reaching back into the Auction.
Deriving state from requirements:
| Requirement | What BidHistory must track |
|---|---|
| "Bids must exceed current highest by increment" | Ordered list of bids |
| "Winner determination varies by type" | Full bid history for strategy queries |
| "Vickrey uses second-highest price" | Access to second-highest bid |
Deriving methods from needs:
| Need from requirements | Method |
|---|---|
| "Record a new bid" | addBid(Bid) |
| "Check current highest for validation" | getHighestBid() |
| "Strategy queries all bids" | getAllBids() |
| "Vickrey needs second-highest" | getSecondHighestBid() |
Key Relationship Decisions
Auction owns BidHistory via composition because bid history does not exist without an auction. If the auction is deleted, its bids go with it.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.