Design a Vending Machine
LLD Case Studies
This is a very common LLD interview question. The goal is to design a machine that dispenses items after a user inserts money and makes a selection. The interviewer is looking for your ability to translate a set of requirements into a clean, object-oriented design.
1. Clarify Requirements
Always start by asking clarifying questions. Don't assume anything.
- What kind of items does it sell? (e.g., drinks, snacks)
- What kind of currency does it accept? (e.g., coins, bills, credit card) For this example, let's assume coins only.
- Does it give change? Yes.
- Can a user cancel their request? Yes, a user can get a full refund before an item is dispensed.
- What happens if the user selects an item that's out of stock? The user should be notified and can select another item or get a refund.
- What happens if the user doesn't insert enough money? The user should be notified of the remaining balance required.
- Are there any admin functions? Yes, an administrator needs to be able to add items and reset the machine.
2. Identify the Core Classes and Objects
Based on the requirements, we can identify the main entities:
VendingMachine
: The main class that orchestrates everything.Item
: Represents the products being sold (e.g., Coke, Pepsi, Chips).Inventory
: A collection or shelf to hold the items.Coin
: An enum representing the types of coins accepted.State
: The machine will behave differently depending on its current state (e.g.,NoCoinInsertedState
,CoinInsertedState
,DispensingState
). This is a perfect use case for the State Design Pattern, and probably what the interviewer is looking for.
3. Design the System - Class Diagram
A class diagram is the best way to visualize the relationships between these classes.
4. Implement the State Pattern
The State pattern allows an object to alter its behavior when its internal state changes. The object appears to change its class.
State Interface:
public interface State {
void insertCoin(VendingMachine machine, Coin coin);
void selectItem(VendingMachine machine, String itemCode);
void dispenseItem(VendingMachine machine);
void cancel(VendingMachine machine);
}
Concrete States:
Each state handles the operations differently.
NoCoinInsertedState
: Can only accept coins. All other operations are invalid.CoinInsertedState
: Can accept more coins, select an item, or be canceled.DispensingState
: A temporary state while the item is being dispensed. No other operations are allowed.
Example: CoinInsertedState
public class CoinInsertedState implements State {
@Override
public void insertCoin(VendingMachine machine, Coin coin) {
machine.addCoin(coin);
System.out.println(coin + " inserted. Current balance: " + machine.getCurrentBalance());
}
@Override
public void selectItem(VendingMachine machine, String itemCode) {
// 1. Check if item exists and is in stock
// 2. Check if enough money has been inserted
// 3. If all good, change state to DispensingState
// 4. If not, notify the user
// ... implementation details ...
if (machine.hasSufficientMoney(itemCode)) {
machine.setState(new DispensingState());
machine.dispenseItem();
} else {
System.out.println("Insufficient funds for " + itemCode);
}
}
// ... other methods
}
5. The VendingMachine
(Context) Class
The VendingMachine
class holds the current state and delegates all actions to it.
public class VendingMachine {
private State currentState;
private Inventory inventory;
private List<Coin> currentCoins;
public VendingMachine() {
this.inventory = new Inventory(); // Fill with items
this.currentState = new NoCoinInsertedState();
this.currentCoins = new ArrayList<>();
}
// The machine delegates all actions to the current state object.
public void insertCoin(Coin coin) {
currentState.insertCoin(this, coin);
}
public void selectItem(String itemCode) {
currentState.selectItem(this, itemCode);
}
// Internal methods to be called by State objects
public void setState(State newState) {
this.currentState = newState;
}
public void addCoin(Coin coin) {
this.currentCoins.add(coin);
}
// ... other getters and setters for inventory, balance, etc.
}
Key Discussion Points
- Why the State Pattern? Be ready to justify your choice. The State pattern is ideal here because the machine's behavior for a given action (like
selectItem
) is fundamentally different depending on its state (e.g., you can't select an item if you haven't inserted any money). It helps you avoid a messyswitch
orif/else
block in the mainVendingMachine
class, adhering to the Open/Closed Principle. - Handling Concurrency: What if two users try to use the machine at the exact same time? You can mention that the core methods (
insertCoin
,selectItem
) should besynchronized
to ensure that only one operation can be processed at a time, maintaining the integrity of the machine's state. - Error Handling and Edge Cases: Discuss how you'd handle running out of a specific coin for change, what happens if an item gets stuck, etc. This shows you're thinking about the robustness of the system.
- Extensibility: How would you add credit card payments? You could create a new
PaymentMethod
interface and haveCoinPayment
andCardPayment
implementations (Strategy Pattern). This demonstrates thinking beyond the immediate problem.