LLDLLD Case StudiesDesign a Vending Machine

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.

VendingMachineInventoryItem<<enum>>Coin<<interface>>StateNoCoinInsertedStateCoinInsertedStateDispensingState1111*11*

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 messy switch or if/else block in the main VendingMachine 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 be synchronized 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 have CoinPayment and CardPayment implementations (Strategy Pattern). This demonstrates thinking beyond the immediate problem.