Core Entities
Auction: auction_id, seller_id, item_id, title, description, start_price, reserve_price (nullable, minimum price to sell), buy_now_price (nullable, instant purchase), start_time, end_time, status (PENDING, ACTIVE, ENDED, CANCELLED), current_bid, current_winner_id, bid_count. Item: item_id, seller_id, category, condition (NEW, USED, REFURBISHED), photos (JSON array), description. Bid: bid_id, auction_id, bidder_id, amount, placed_at, is_proxy (auto-bid), max_proxy_amount (for proxy bids). AuctionWatch: auction_id, user_id (users watching the auction for notifications).
Bid Placement with Concurrency
class AuctionService:
def place_bid(self, auction_id: int, bidder_id: int,
amount: Decimal, max_proxy: Decimal = None) -> Bid:
with self.db.transaction():
auction = self.db.lock_auction(auction_id) # SELECT FOR UPDATE
if auction.status != AuctionStatus.ACTIVE:
raise AuctionNotActiveError()
if datetime.now() > auction.end_time:
raise AuctionEndedError()
if amount = amount:
# Proxy auto-bids: counter with the minimum needed
counter = min(proxy.max_proxy_amount, amount + MIN_INCREMENT)
self.db.update_auction_bid(auction_id, counter, auction.current_winner_id)
bid = Bid(auction_id=auction_id, bidder_id=bidder_id,
amount=amount, is_proxy=False)
self.db.save(bid)
self.notify_outbid(bidder_id, auction_id, counter)
return bid
# New winning bid
self.db.update_auction(auction_id, current_bid=amount, current_winner_id=bidder_id)
if max_proxy:
self.db.save_proxy_bid(auction_id, bidder_id, max_proxy)
bid = Bid(auction_id=auction_id, bidder_id=bidder_id,
amount=amount, is_proxy=False, max_proxy_amount=max_proxy)
self.db.save(bid)
if auction.current_winner_id and auction.current_winner_id != bidder_id:
self.notify_outbid(auction.current_winner_id, auction_id, amount)
return bid
Proxy Bidding (Automatic Bidding)
Proxy bidding lets users set a maximum bid without revealing it. The system automatically increments their bid by the minimum increment when outbid, up to their maximum. Example: current bid is $10, increment is $1. User A sets a proxy max of $50. User B bids $15 — proxy auto-counters with $16 (just enough to beat). User B bids $55 — exceeds A’s max of $50, so B wins at $51. The proxy bid maximum is stored encrypted or hashed in the Bid record. Only the auction service reads it to compute proxy counters. Never expose another bidder’s proxy max to any user — it is confidential strategic information.
Auction End and Sniping Prevention
Sniping: placing a bid in the last seconds before auction end to win without giving others a chance to respond. Prevention: extend the auction end time by N minutes (e.g., 5 minutes) whenever a bid is placed in the last N minutes. This gives other bidders a fair chance to respond. Track extended_end_time separately from original end_time. The auction ends when extended_end_time is reached with no bids in the last N minutes.
Auction end processing: a scheduler fires at the auction’s end_time. Check if no bid was placed in the last N minutes (use extended_end_time). If auction.current_bid = reserve_price: auction succeeds. Notify the winner and seller. Create an order record. Process payment (charge the winner’s card via payment gateway). If payment fails: move to the next-highest bidder. Update auction status to ENDED.
Real-Time Bid Updates
All watchers and current participants need to see the current highest bid in real time. WebSocket for active bidders (users with the auction page open). When a new bid is placed: publish a bid event to Kafka, partitioned by auction_id. A broadcast service subscribes and pushes the update to all WebSocket connections watching that auction. Push notifications for outbid users (FCM/APNs). Email digest for watched auctions (one email per auction, not per bid — rate-limited). WebSocket server: each server holds a set of auction_id → {connection1, connection2}. When a bid event arrives for auction X, broadcast to all connections watching X. Use a pub/sub Redis channel per auction_id for multi-server setups.
Fraud Prevention
Shill bidding: the seller creates fake accounts to drive up the price. Detection: if a bidder account was created recently ( 50% of auctions fail to meet reserve.
Asked at: Airbnb Interview Guide
Asked at: Shopify Interview Guide
Asked at: Coinbase Interview Guide
Asked at: Stripe Interview Guide