Last Updated: December 19, 2025
An Online Auction System is a digital platform that facilitates the buying and selling of items through competitive bidding, typically over a fixed time window. Sellers can list products for auction, and buyers can place incremental bids to compete for ownership.
Select an auction to view details
At the end of the auction period, the highest bidder wins, and the system finalizes the transaction. Examples of such platforms include eBay, Sotheby’s, and GovDeals.
In this chapter, we will explore the low-level design of an online auction system in detail.
Let's start by clarifying the requirements:
Before starting the design, it's important to ask thoughtful questions to uncover hidden assumptions, clarify ambiguities, and define the system's scope more precisely.
Here is an example of how a conversation between the candidate and the interviewer might unfold:
Candidate: Can a user place bids on multiple items simultaneously?
Interviewer: Yes, users should be able to participate in multiple ongoing auctions at the same time.
Candidate: How is the winner of an auction determined? Is it simply the highest bid at the end time?
Interviewer: Correct. The user with the highest bid when the auction ends wins the item. In case of a tie, the earliest bid should be considered the winner.
Candidate: Should users receive notifications when they’re outbid or when an auction they’re involved in ends?
Interviewer: Yes, users should be notified when they’re outbid, and when an auction they’re participating in ends.
Candidate: Should we keep a record of all bids for an item?
Interviewer: Yes, the system should maintain a complete bid history for each auction item.
Candidate: What about post-auction activities like payment processing and shipping?
Interviewer: Let's consider those out of scope. The system's responsibility ends once the auction is closed and a winner is declared.
After gathering the details, we can summarize the key system requirements.
Core entities are the fundamental building blocks of our system. We identify them by analyzing key nouns (e.g., user, auction, bid, item, notification) and actions (e.g., create, bid, outbid, win, notify) from the functional requirements. These often translate directly into classes, enums, or interfaces in an object-oriented design.
Let’s walk through the functional requirements and extract the relevant entities:
This points to three fundamental entities:
User: Represents a participant in the auction, who can act as a bidder.Auction: The central entity representing a single item up for auction. It holds the item's details, manages its state, and maintains a history of all bids.Bid: A data object representing a single bid made by a User on an Auction, containing the amount and a timestamp.An Auction has a clear lifecycle (e.g., active, closed). This is best represented by an AuctionState enum, which helps control what actions are permissible at different times.
To manage the lifecycle of multiple auctions and provide a clean entry point, a central service layer is necessary.
AuctionService: This class acts as a Facade and Singleton. It provides a simple, high-level API for all client interactions (like creating an auction or placing a bid), hiding the complexity of managing auction states, notifications, and scheduling.These core entities define the essential abstractions of the Online Auction System and will guide the structure of your low-level design and class diagrams.
This section breaks down the system's architecture into its fundamental classes, their responsibilities, and the relationships that connect them. We also explore the key design patterns that provide robustness and flexibility to the solution.
The system is composed of several types of classes, each with a distinct role.
AuctionStateDefines the discrete lifecycle stages of an auction: PENDING, ACTIVE, and CLOSED.
This enum is used for state management within the Auction class.
BidA data class representing a single bid made by a user.
It encapsulates the bidder, the amount, and a timestamp. It implements Comparable to allow for easy determination of the highest bid, first by amount and then by timestamp.
UserRepresents a participant in the auction.
Crucially, it also acts as a concrete Observer by implementing the AuctionObserver interface. This allows a User object to be directly subscribed to an auction and receive notifications.
AuctionThis is the central class representing a single auction event.
It manages all bids, the auction's state (AuctionState), and the end time. It acts as the Subject in the Observer pattern, maintaining a list of subscribed observers (Users) and notifying them of key events, such as when a new highest bid is placed or when the auction ends.
AuctionService (Singleton & Facade)The primary entry point for the entire application.
It orchestrates all high-level operations, such as creating users and auctions, and processing bids. It manages the collections of all active users and auctions, and it uses a ScheduledExecutorService to handle the automatic closing of auctions when their end time is reached.
The relationships between classes define the system's structure and data flow.
AuctionService "has-a" collection of Users and Auctions, managing their lifecycle within the system.Auction "has-a" list of Bids.Auction (Subject) is associated with a set of AuctionObservers (which are Users).Bid is associated with the User who placed it.User class implements the AuctionObserver interface, allowing it to act as an observer.AuctionService (Facade) depends on Auction and User objects to carry out its operations.Auction class depends on the Bid class to record bidding activity.This is the core pattern for providing real-time updates to participants.
The Auction object acts as the Subject, and bidding Users act as Observers. When a significant event occurs (e.g., a user is outbid, the auction ends), the Auction automatically notifies all relevant observers, ensuring they are kept in sync with the auction's state.
The AuctionService class serves as a facade. It provides a simple, high-level API (createAuction, placeBid, endAuction) that hides the complex internal workflows of managing users, auctions, bidding logic, and scheduling. Clients interact with the system through this single, clean interface.
AuctionService is implemented as a singleton to ensure there is a single, globally accessible point of control for the entire auction system. This centralizes the management of all auctions and users and prevents state inconsistencies.
Defines the Observer interface to receive updates about an auction’s state (e.g., when outbid or when the auction ends). All participants placing bids become observers automatically.
1class AuctionObserver(ABC):
2 @abstractmethod
3 def on_update(self, auction: 'Auction', message: str):
4 passThe User class represents a participant, and crucially, it also acts as an "Observer" to receive notifications.
1class User(AuctionObserver):
2 def __init__(self, name: str):
3 self.id = str(uuid.uuid4())
4 self.name = name
5
6 def get_id(self) -> str:
7 return self.id
8
9 def get_name(self) -> str:
10 return self.name
11
12 def on_update(self, auction: 'Auction', message: str):
13 print(f"--- Notification for {self.name} ---")
14 print(f"Auction: {auction.get_item_name()}")
15 print(f"Message: {message}")
16 print("---------------------------\n")Each user has a unique ID and name.
By implementing AuctionObserver, the User class can be directly subscribed to an Auction. The onUpdate method is the callback that the Auction will invoke to send notifications, such as "You have been outbid" or "The auction has ended." This decouples the auction logic from the user notification mechanism.
Represents the current lifecycle state of an auction.
1class AuctionState(Enum):
2 PENDING = "PENDING"
3 ACTIVE = "ACTIVE"
4 CLOSED = "CLOSED"A data object representing a single bid, designed to be comparable for easily finding the highest bid.
1class Bid:
2 def __init__(self, bidder: User, amount: Decimal):
3 self.bidder = bidder
4 self.amount = amount
5 self.timestamp = datetime.now()
6
7 def get_bidder(self) -> User:
8 return self.bidder
9
10 def get_amount(self) -> Decimal:
11 return self.amount
12
13 def get_timestamp(self) -> datetime:
14 return self.timestamp
15
16 def __lt__(self, other: 'Bid') -> bool:
17 if self.amount != other.amount:
18 return self.amount < other.amount
19 return self.timestamp > other.timestamp
20
21 def __eq__(self, other: 'Bid') -> bool:
22 return self.amount == other.amount and self.timestamp == other.timestamp
23
24 def __le__(self, other: 'Bid') -> bool:
25 return self < other or self == other
26
27 def __gt__(self, other: 'Bid') -> bool:
28 return not self <= other
29
30 def __ge__(self, other: 'Bid') -> bool:
31 return not self < other
32
33 def __ne__(self, other: 'Bid') -> bool:
34 return not self == other
35
36 def __str__(self) -> str:
37 return f"Bidder: {self.bidder.get_name()}, Amount: {self.amount:.2f}, Time: {self.timestamp}"Each Bid is timestamped and comparable by amount (higher is better) and time (earlier wins in case of tie). This makes sorting and comparison straightforward.
This is the central class for a single auction. It manages the bidding process, its own state, and acts as the "Subject" in the Observer pattern.
1class Auction:
2 def __init__(self, item_name: str, description: str, starting_price: Decimal, end_time: datetime):
3 self.id = str(uuid.uuid4())
4 self.item_name = item_name
5 self.description = description
6 self.starting_price = starting_price
7 self.end_time = end_time
8 self.bids: List[Bid] = []
9 self.observers: Set[AuctionObserver] = set()
10 self.state = AuctionState.ACTIVE
11 self.winning_bid: Optional[Bid] = None
12 self._lock = threading.RLock()
13
14 def place_bid(self, bidder: User, amount: Decimal):
15 with self._lock:
16 if self.state != AuctionState.ACTIVE:
17 raise Exception("Auction is not active.")
18 if datetime.now() > self.end_time:
19 self.end_auction()
20 raise Exception("Auction has already ended.")
21
22 highest_bid = self.get_highest_bid()
23 current_max_amount = self.starting_price if highest_bid is None else highest_bid.get_amount()
24
25 if amount <= current_max_amount:
26 raise ValueError("Bid must be higher than the current highest bid.")
27
28 previous_highest_bidder = highest_bid.get_bidder() if highest_bid is not None else None
29
30 new_bid = Bid(bidder, amount)
31 self.bids.append(new_bid)
32 self.add_observer(bidder)
33
34 print(f"SUCCESS: {bidder.get_name()} placed a bid of ${amount:.2f} on '{self.item_name}'.")
35
36 if previous_highest_bidder is not None and previous_highest_bidder != bidder:
37 self.notify_observer(previous_highest_bidder,
38 f"You have been outbid on '{self.item_name}'! The new highest bid is ${amount:.2f}.")
39
40 def end_auction(self):
41 with self._lock:
42 if self.state != AuctionState.ACTIVE:
43 return
44
45 self.state = AuctionState.CLOSED
46 self.winning_bid = self.get_highest_bid()
47
48 if self.winning_bid is not None:
49 end_message = f"Auction for '{self.item_name}' has ended. Winner is {self.winning_bid.get_bidder().get_name()} with a bid of ${self.winning_bid.get_amount():.2f}!"
50 else:
51 end_message = f"Auction for '{self.item_name}' has ended. There were no bids."
52
53 print(f"\n{end_message.upper()}")
54 self.notify_all_observers(end_message)
55
56 def get_highest_bid(self) -> Optional[Bid]:
57 if not self.bids:
58 return None
59 return max(self.bids)
60
61 def is_active(self) -> bool:
62 return self.state == AuctionState.ACTIVE
63
64 def add_observer(self, observer: AuctionObserver):
65 self.observers.add(observer)
66
67 def notify_all_observers(self, message: str):
68 for observer in self.observers:
69 observer.on_update(self, message)
70
71 def notify_observer(self, observer: AuctionObserver, message: str):
72 observer.on_update(self, message)
73
74 def get_id(self) -> str:
75 return self.id
76
77 def get_item_name(self) -> str:
78 return self.item_name
79
80 def get_bid_history(self) -> List[Bid]:
81 return self.bids.copy()
82
83 def get_state(self) -> AuctionState:
84 return self.state
85
86 def get_winning_bid(self) -> Optional[Bid]:
87 return self.winning_bidThis class acts as a central Singleton and Facade, providing a simplified API for clients to interact with the entire auction system.
1class AuctionService:
2 _instance = None
3 _lock = threading.Lock()
4
5 def __init__(self):
6 if AuctionService._instance is not None:
7 raise Exception("This class is a singleton!")
8 self.users: Dict[str, User] = {}
9 self.auctions: Dict[str, Auction] = {}
10 self.scheduler = ThreadPoolExecutor(max_workers=10)
11 self._shutdown = False
12
13 @staticmethod
14 def get_instance():
15 if AuctionService._instance is None:
16 with AuctionService._lock:
17 if AuctionService._instance is None:
18 AuctionService._instance = AuctionService()
19 return AuctionService._instance
20
21 def create_user(self, name: str) -> User:
22 user = User(name)
23 self.users[user.get_id()] = user
24 return user
25
26 def get_user(self, user_id: str) -> User:
27 return self.users[user_id]
28
29 def create_auction(self, item_name: str, description: str, starting_price: Decimal, end_time: datetime) -> Auction:
30 auction = Auction(item_name, description, starting_price, end_time)
31 self.auctions[auction.get_id()] = auction
32
33 delay = (end_time - datetime.now()).total_seconds()
34 if delay > 0:
35 self.scheduler.submit(self._scheduled_end_auction, auction.get_id(), delay)
36
37 print(f"New auction created for '{item_name}' (ID: {auction.get_id()}), ending at {end_time}.")
38 return auction
39
40 def _scheduled_end_auction(self, auction_id: str, delay: float):
41 time.sleep(delay)
42 if not self._shutdown:
43 self.end_auction(auction_id)
44
45 def view_active_auctions(self) -> List[Auction]:
46 return [auction for auction in self.auctions.values() if auction.is_active()]
47
48 def place_bid(self, auction_id: str, bidder_id: str, amount: Decimal):
49 auction = self.get_auction(auction_id)
50 auction.place_bid(self.users[bidder_id], amount)
51
52 def end_auction(self, auction_id: str):
53 auction = self.get_auction(auction_id)
54 auction.end_auction()
55
56 def get_auction(self, auction_id: str) -> Auction:
57 auction = self.auctions.get(auction_id)
58 if auction is None:
59 raise KeyError(f"Auction with ID {auction_id} not found.")
60 return auction
61
62 def shutdown(self):
63 self._shutdown = True
64 self.scheduler.shutdown(wait=True)This driver class simulates a real-world bidding scenario, validating the end-to-end functionality of the system.
1class AuctionSystemDemo:
2 @staticmethod
3 def main():
4 auction_service = AuctionService.get_instance()
5
6 alice = auction_service.create_user("Alice")
7 bob = auction_service.create_user("Bob")
8 carol = auction_service.create_user("Carol")
9
10 print("=============================================")
11 print(" Online Auction System Demo ")
12 print("=============================================")
13
14 end_time = datetime.now() + timedelta(seconds=10)
15 laptop_auction = auction_service.create_auction(
16 "Vintage Laptop",
17 "A rare 1990s laptop, in working condition.",
18 Decimal("100.00"),
19 end_time
20 )
21 print()
22
23 try:
24 auction_service.place_bid(laptop_auction.get_id(), alice.get_id(), Decimal("110.00"))
25 time.sleep(0.5)
26
27 auction_service.place_bid(laptop_auction.get_id(), bob.get_id(), Decimal("120.00"))
28 time.sleep(0.5)
29
30 auction_service.place_bid(laptop_auction.get_id(), carol.get_id(), Decimal("125.00"))
31 time.sleep(0.5)
32
33 auction_service.place_bid(laptop_auction.get_id(), alice.get_id(), Decimal("150.00"))
34
35 print("\n--- Waiting for auction to end automatically... ---")
36 time.sleep(2)
37 except Exception as e:
38 print(f"An error occurred during bidding: {e}")
39
40 print("\n--- Post-Auction Information ---")
41 ended_auction = auction_service.get_auction(laptop_auction.get_id())
42
43 if ended_auction.get_winning_bid() is not None:
44 print(f"Final Winner: {ended_auction.get_winning_bid().get_bidder().get_name()}")
45 print(f"Winning Price: ${ended_auction.get_winning_bid().get_amount():.2f}")
46 else:
47 print("The auction ended with no winner.")
48
49 print("\nFull Bid History:")
50 for bid in ended_auction.get_bid_history():
51 print(bid)
52
53 print("\n--- Attempting to bid on an ended auction ---")
54 try:
55 auction_service.place_bid(laptop_auction.get_id(), bob.get_id(), Decimal("200.00"))
56 except Exception as e:
57 print(f"CAUGHT EXPECTED ERROR: {e}")
58
59 auction_service.shutdown()
60
61
62if __name__ == "__main__":
63 AuctionSystemDemo.main()Which entity is primarily responsible for holding the list of bids in an Online Auction System?
No comments yet. Be the first to comment!