Hello! Welcome to our final case study in the Low-Level Design section of the course.
In our last lesson, we designed a chess game. The core challenge was modeling the unique movement rules for each piece. We solved this using the Strategy pattern, which allowed us to encapsulate each movement algorithm and make them interchangeable. This was a classic example of designing for complex, varied behaviors.
Today, we'll tackle another classic LLD problem that emphasizes a different, but equally important, behavioral pattern. Your learning outcome is to design a vending machine, implementing state transitions, payment, and inventory control.
A vending machine is a fantastic example of a system whose behavior is dictated by its current state. Is it idle? Has a user inserted money? Is it dispensing an item? We'll use this problem to dive deep into the State Design Pattern.
Step 1: Clarifying the Requirements
As always, we begin by understanding and defining the scope of our system. A vending machine seems simple, but there are many corner cases to consider.
🚀 Vending Machine System Design – LLD for Interviews & Projects 🧑💻
Let's start by walking through the basic functionalities of a vending machine from a user's perspective. The video 'Vending Machine System Design' by codeWithAryan provides a good overview of the features we need to consider.
Please watch from 01:22 to 07:34. This section covers: The basic user interaction flow with a vending machine. Identifying the core system responsibilities: managing inventory, processing payments, and dispensing products. How to approach this problem in an interview by first enacting the process and gathering requirements. A list of the machine's potential states (idle, ready, payment, etc.).
From the video and general knowledge, let's establish a set of functional requirements for our Minimum Viable Product (MVP):
- Inventory Management: The machine must maintain an inventory of items, each with a name, price, and quantity.
- Item Selection: A user must be able to select an item from the available inventory.
- Payment: The system must accept payment. For our initial design, we will focus on coin-based payments. We'll design it to be extensible for other methods (cards, UPI) later.
- Transaction Logic:
- Dispense the item and calculate/return change if payment is successful and exceeds the item price.
- Reject the transaction if the item is out of stock.
- Handle insufficient funds (e.g., allow the user to insert more coins or cancel).
- Allow the user to cancel the transaction and get a full refund before an item is dispensed.
- State Management: The machine must correctly transition between different operational states.
Step 2: The Heart of the Machine - States and Transitions
The most critical part of this design is modeling the machine's state. A vending machine operates as a finite-state machine. An action (like inserting a coin) causes a transition from one state to another.
The primary states we can identify are:
- Idle State: The machine is waiting for a user to start a transaction.
- HasMoney State: The user has inserted some money, but has not yet selected an item. The machine is ready for a selection.
- Selection State: The user has selected an item. The machine needs to validate the selection and payment.
- Dispensing State: The payment is validated, and the machine is dispensing the product (and any change).
- OutOfStock State: The machine cannot fulfill requests because it's empty or a specific item is sold out.
This flow of states and the events that trigger transitions can be visualized with a state diagram.

🚀 Vending Machine System Design – LLD for Interviews & Projects 🧑💻
To see a more detailed walkthrough of these state transitions, let's return to the 'Vending Machine System Design' video.
Watch the segment from 19:28 to 23:09. The presenter does an excellent job of drawing out the state diagram and explaining the transitions between Idle, Has Money, Selection, Dispense, and Out of Stock states based on user actions.
Step 3: Applying the State Design Pattern
A naive way to implement this would be to have a single VendingMachine class with a large switch statement that checks the currentState variable and behaves accordingly.
// AVOID THIS anti-pattern
public class VendingMachine {
private State currentState;
public void insertCoin(Coin c) {
switch(currentState) {
case IDLE:
// logic for inserting coin in idle state
this.currentState = State.HAS_MONEY;
break;
case HAS_MONEY:
// logic for inserting more coins
break;
// ... other cases
}
}
// ... other methods with similar switch statements
}
This violates the Open/Closed Principle. Adding a new state (e.g., MaintenanceState) would require modifying every method in the VendingMachine class.
A much cleaner solution is the State Pattern. It allows an object to change its behavior when its internal state changes. The object will appear to change its class.
The pattern consists of three main components:
- Context (
VendingMachine): This class maintains an instance of a Concrete State that defines the current behavior. It delegates state-specific requests to the current state object. - State (Interface or Abstract Class): This defines a common interface for all classes that represent a state. This interface will have methods for all possible actions (e.g.,
insertCoin(),selectProduct()). - Concrete States (
IdleState,HasMoneyState, etc.): These classes implement the State interface. Each class provides the actual implementation for an action in that specific state. They are also responsible for transitioning the Context to a new state.
How to use State Design Pattern in Java? Vending ...
The article 'How to use State Design Pattern in Java? Vending...' from javarevisited.blogspot.com provides an excellent conceptual overview and motivation for using this pattern for our problem.
Please read the introductory section of this article. It clearly explains how the vending machine's operations can be mapped to different states and introduces the idea of delegating actions to state objects.
Here is how the structure looks in code:
The State Interface:
// State.java
public interface VendingMachineState {
void insertCoin(VendingMachine machine, Coin coin);
void selectProduct(VendingMachine machine, int productCode);
void dispenseProduct(VendingMachine machine);
void cancel(VendingMachine machine);
}
A Concrete State Implementation:
// IdleState.java
public class IdleState implements VendingMachineState {
@Override
public void insertCoin(VendingMachine machine, Coin coin) {
System.out.println("Coin inserted.");
machine.addCoin(coin);
// Transition to the next state
machine.setCurrentState(new HasMoneyState());
}
@Override
public void selectProduct(VendingMachine machine, int productCode) {
System.out.println("Please insert a coin first.");
}
// other methods would also print error messages
...
}
The Context Class:
// VendingMachine.java
public class VendingMachine {
private VendingMachineState currentState;
private Inventory inventory;
private List<Coin> currentCoins;
public VendingMachine() {
// Initial state
this.currentState = new IdleState();
// ... initialize inventory
}
public void setCurrentState(VendingMachineState state) {
this.currentState = state;
}
// Delegate actions to the current state object
public void insertCoin(Coin coin) {
currentState.insertCoin(this, coin);
}
public void selectProduct(int productCode) {
currentState.selectProduct(this, productCode);
}
// ... other methods and helpers
}
This design is clean, maintainable, and follows SOLID principles. Each state's logic is encapsulated in its own class.
Step 4: Designing Core Entities and Final Structure
With the state management figured out, let's define the other key classes.
🚀 Vending Machine System Design – LLD for Interviews & Projects 🧑💻
Let's review the classes needed to represent the physical and logical parts of the machine, such as items, coins, and inventory.
Please watch the video from 10:30 to 13:54 and 23:09 to 34:58. These segments cover: The main entities: Item, ItemShelf, Inventory, and Coin. The implementation of the VendingMachine class as the context that holds the inventory and current state. A detailed walkthrough of the code for each concrete state (IdleState, HasMoneyState, etc.) and how they interact with the context to trigger transitions.
Based on the videos and articles, we arrive at a comprehensive class structure. The following UML diagram shows how all the pieces fit together, including the State pattern, inventory management, and payment components.
Test your understanding!
In the HasMoneyState, the user can either select a product or press a cancel button to get their money back. How would you implement the cancel() method in the HasMoneyState class? What should happen?
Show answer
The cancel() method within the HasMoneyState class would be responsible for two things:
- Refunding Money: It would call a helper method on the
VendingMachine(the context object) to return the list of coins the user has inserted so far.machine.refundAllCoins(). This helper would clear the machine's internal coin buffer. - Transitioning State: After initiating the refund, it must transition the machine's state back to the beginning. It would call
machine.setCurrentState(new IdleState());.
This ensures the machine is reset to its initial state, ready for the next customer.
Step 5: Designing for Extensibility (Strategy Pattern)
Our current design handles coins, but what if we want to add support for credit cards or mobile payments like UPI? We can use the Strategy Pattern again!
We can define a PaymentStrategy interface with a processPayment(double amount) method. Then, we create concrete strategies like CoinPaymentStrategy, CardPaymentStrategy, and UpiPaymentStrategy.
The VendingMachine can be configured with a specific strategy or even offer multiple strategies to the user.
Low Level Design - Design a Vending Machine
The blog post 'Low Level Design - Design a Vending Machine' by Ujjwal Bhardwaj has a concise section on using design patterns for extensibility. Let's see how it applies the Strategy and Adapter patterns.
Read the subsections '2. Strategy Pattern' and '3. Adapter Pattern'. Note how: Strategy is used to encapsulate different payment algorithms. Adapter is used to integrate with external, third-party payment gateways that might have incompatible interfaces.
This demonstrates how different design patterns can be combined to solve different aspects of a single problem: State for managing the object's core lifecycle, and Strategy/Adapter for making parts of its behavior interchangeable and extensible.
Your Turn: Study the Full Implementation
You now have a solid grasp of the concepts and design patterns required. To solidify your understanding, it's time to review a complete implementation.
How to use State Design Pattern in Java? Vending ...
The javarevisited article we looked at earlier provides a full set of Java files for a vending machine built with the State pattern.
Please review the code provided in the article. Start with 'How to Design Vending Machine in Java...' and study the code for each file listed (Coin.java, State.java, Idle.java, VendingMachine.java, etc.). Focus on how the VendingMachine class delegates calls to the current state object, and how each concrete state class handles its logic and triggers the next state transition. Finally, look at the VendingMachineTest.java file to see how the system is tested.
Conclusion
Congratulations on completing the final LLD case study! Designing a vending machine provided an excellent platform to explore state-driven design in depth.
Key Takeaways:
- Stateful Systems: Many real-world systems can be modeled as finite-state machines. Identifying the states and the events that trigger transitions is a crucial first step.
- The State Pattern: This pattern is the ideal solution for managing an object whose behavior changes based on its internal state. It helps avoid large, unmanageable conditional blocks and adheres to SOLID principles. Each state's logic is neatly encapsulated in its own class.
- Combining Patterns: We saw how the State pattern could manage the machine's lifecycle while the Strategy pattern could provide flexibility for independent algorithms like payment processing.
- Context is Key: The
Contextclass (VendingMachine) plays a vital role in holding the current state and shared data (like inventory), while theConcrete Stateclasses control the behavior and transitions.
This lesson concludes our deep dive into Low-Level Design. You have now analyzed and designed systems that are data-centric (Hotel Booking), rule-centric (Chess), and state-centric (Vending Machine), applying key OOP principles and design patterns along the way.
You are now well-prepared to move to the next major topic in our course: High-Level Design (HLD). We will shift our focus from the internal structure of a single service to designing large-scale distributed systems, starting with the fundamental principles of scalability, availability, and reliability.