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.
Always start by asking clarifying questions. Don't assume anything.
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.A class diagram is the best way to visualize the relationships between these classes.
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
}
VendingMachine (Context) ClassThe 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.
}
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 messy switch or if/else block in the main VendingMachine class, adhering to the Open/Closed Principle.insertCoin, selectItem) should be synchronized to ensure that only one operation can be processed at a time, maintaining the integrity of the machine's state.PaymentMethod interface and have CoinPayment and CardPayment implementations (Strategy Pattern). This demonstrates thinking beyond the immediate problem.