Back to LLD Problems: Parking Lot, Elevator, Chess, etc.
Q8Question 8 of 12

Design a Vending Machine

Key Concepts
  • The vending machine is modelled with the State pattern: IdleState, SelectingState, DispensingState, and ReturningChangeState.
  • Each state class permits only the transitions that make sense and rejects the rest, eliminating boolean-flag spaghetti.
  • A Slot holds a Product and a count, forming the inventory; the machine checks both stock and sufficient funds before dispensing.
  • Money handling uses BigDecimal throughout because a machine dealing in rupees cannot tolerate floating-point rounding.
  • Change computation works against available coin denominations and fails gracefully to exact-change-only mode when it cannot make change.
  • The State pattern makes illegal transitions impossible by construction; adding a maintenance mode is just a new state class.
  • At National Bank of India, self-service kiosks dispensing cheque books and passbooks use exactly this state-machine design.

Definition

A vending machine is a state machine that collects money, validates stock, dispenses a product, and returns change.
  • VendingMachine – the context object that holds the current MachineState and delegates every action to it.
  • MachineState – interface with actions like insertMoney(), selectProduct(), and dispense().
  • Concrete states – IdleState, SelectingState, DispensingState, ReturningChangeState, each permitting only valid transitions.
  • Slot – holds a Product and an inventory count.
  • Product – carries a name and a BigDecimal price.
  • Change computation – works against available coin denominations and falls back to exact-change-only mode.

Picture a self-service kiosk at National Bank of India that dispenses passbooks. Rajesh walks up (idle), picks a passbook type (selecting), inserts coins, the kiosk checks it has enough money and stock, dispenses the passbook, and returns any extra coins as change. Each stage has strict rules about what is allowed next, exactly like the vending machine's states.

Understanding the Concepts

What are the core entities?

The core entities are VendingMachine, MachineState, concrete state classes, Product, and Slot.

JAVA
package com.bank.vending.model;

import java.math.BigDecimal;

public class Product {
    private final String name;
    private final BigDecimal price;

    public Product(String name, BigDecimal price) {
        this.name = name;
        this.price = price;
    }

    public String getName() { return name; }
    public BigDecimal getPrice() { return price; }
}
JAVA
package com.bank.vending.model;

public class Slot {
    private final String slotId;
    private final Product product;
    private int quantity;

    public Slot(String slotId, Product product, int quantity) {
        this.slotId = slotId;
        this.product = product;
        this.quantity = quantity;
    }

    public boolean hasStock() { return quantity > 0; }

    public void dispense() {
        if (!hasStock()) throw new IllegalStateException(
                "Slot " + slotId + " is empty");
        quantity--;
    }

    public String getSlotId() { return slotId; }
    public Product getProduct() { return product; }
    public int getQuantity() { return quantity; }
}

How does the State pattern drive the machine?

The VendingMachine holds a current state and delegates every action to it. Each state class permits only the transitions that make sense.

JAVA
package com.bank.vending.state;

import com.bank.vending.model.VendingMachine;
import java.math.BigDecimal;

public interface MachineState {
    void insertMoney(VendingMachine machine, BigDecimal amount);
    void selectProduct(VendingMachine machine, String slotId);
    void dispense(VendingMachine machine);
    void returnChange(VendingMachine machine);
}
JAVA
package com.bank.vending.state;

import com.bank.vending.model.VendingMachine;
import java.math.BigDecimal;

public class IdleState implements MachineState {
    @Override
    public void insertMoney(VendingMachine machine, BigDecimal amount) {
        machine.addBalance(amount);
        machine.setState(new SelectingState());
        System.out.println("Inserted " + amount
                + ", balance: " + machine.getBalance());
    }

    @Override
    public void selectProduct(VendingMachine machine, String slotId) {
        System.out.println("Insert money first");
    }

    @Override
    public void dispense(VendingMachine machine) {
        System.out.println("Insert money first");
    }

    @Override
    public void returnChange(VendingMachine machine) {
        System.out.println("No money to return");
    }
}
JAVA
package com.bank.vending.state;

import com.bank.vending.model.*;
import java.math.BigDecimal;

public class SelectingState implements MachineState {
    @Override
    public void insertMoney(VendingMachine machine, BigDecimal amount) {
        machine.addBalance(amount);
        System.out.println("Added " + amount
                + ", balance: " + machine.getBalance());
    }

    @Override
    public void selectProduct(VendingMachine machine, String slotId) {
        Slot slot = machine.getSlot(slotId);
        if (!slot.hasStock()) {
            System.out.println("Out of stock: " + slotId);
            return;
        }
        if (machine.getBalance().compareTo(
                slot.getProduct().getPrice()) < 0) {
            System.out.println("Insufficient funds. Need "
                    + slot.getProduct().getPrice()
                    + ", have " + machine.getBalance());
            return;
        }
        machine.setSelectedSlot(slot);
        machine.setState(new DispensingState());
        machine.dispense();
    }

    @Override
    public void dispense(VendingMachine machine) {
        System.out.println("Select a product first");
    }

    @Override
    public void returnChange(VendingMachine machine) {
        System.out.println("Returning " + machine.getBalance());
        machine.resetBalance();
        machine.setState(new IdleState());
    }
}
JAVA
package com.bank.vending.state;

import com.bank.vending.model.VendingMachine;
import java.math.BigDecimal;

public class DispensingState implements MachineState {
    @Override
    public void insertMoney(VendingMachine machine, BigDecimal amount) {
        System.out.println("Please wait, dispensing...");
    }

    @Override
    public void selectProduct(VendingMachine machine, String slotId) {
        System.out.println("Please wait, dispensing...");
    }

    @Override
    public void dispense(VendingMachine machine) {
        var slot = machine.getSelectedSlot();
        slot.dispense();
        BigDecimal price = slot.getProduct().getPrice();
        machine.subtractBalance(price);
        System.out.println("Dispensed: " + slot.getProduct().getName());
        machine.setState(new ReturningChangeState());
        machine.returnChange();
    }

    @Override
    public void returnChange(VendingMachine machine) {
        System.out.println("Please wait, dispensing...");
    }
}

How does the VendingMachine context work?

JAVA
package com.bank.vending.model;

import com.bank.vending.state.*;
import java.math.BigDecimal;
import java.util.Map;

public class VendingMachine {
    private MachineState state;
    private BigDecimal balance;
    private final Map<String, Slot> slots;
    private Slot selectedSlot;

    public VendingMachine(Map<String, Slot> slots) {
        this.slots = slots;
        this.state = new IdleState();
        this.balance = BigDecimal.ZERO;
    }

    public void insertMoney(BigDecimal amount) {
        state.insertMoney(this, amount);
    }

    public void selectProduct(String slotId) {
        state.selectProduct(this, slotId);
    }

    public void dispense() { state.dispense(this); }
    public void returnChange() { state.returnChange(this); }

    public void setState(MachineState state) { this.state = state; }
    public BigDecimal getBalance() { return balance; }
    public void addBalance(BigDecimal amount) {
        balance = balance.add(amount);
    }
    public void subtractBalance(BigDecimal amount) {
        balance = balance.subtract(amount);
    }
    public void resetBalance() { balance = BigDecimal.ZERO; }
    public Slot getSlot(String id) { return slots.get(id); }
    public Slot getSelectedSlot() { return selectedSlot; }
    public void setSelectedSlot(Slot slot) { this.selectedSlot = slot; }
}
TEXT
Inserted 50, balance: 50
Dispensed: Passbook (Standard)
Change returned: 20
Machine idle.

Why use the State pattern instead of boolean flags?

The naive alternative is a tangle of boolean flags like isDispensing and hasMoney, which inevitably produces illegal states such as dispensing while still collecting money. By making each phase its own class, the illegal transitions become impossible by construction.

JAVA
// Wrong: boolean flags, fragile
boolean hasMoney = true;
boolean isDispensing = true; // illegal combination!

// Correct: State pattern, each phase is a class
machine.setState(new DispensingState()); // only dispense actions work

How does change computation work?

Change is computed against available coin denominations, using a greedy approach. If the machine cannot make exact change, it drops into exact-change-only mode.

JAVA
package com.bank.vending.service;

import java.math.BigDecimal;
import java.util.Map;
import java.util.TreeMap;
import java.util.Collections;

public class ChangeCalculator {
    private final TreeMap<BigDecimal, Integer> coinInventory;

    public ChangeCalculator(Map<BigDecimal, Integer> coins) {
        this.coinInventory = new TreeMap<>(Collections.reverseOrder());
        this.coinInventory.putAll(coins);
    }

    public boolean canMakeChange(BigDecimal amount) {
        BigDecimal remaining = amount;
        for (var entry : coinInventory.entrySet()) {
            BigDecimal coin = entry.getKey();
            int count = entry.getValue();
            while (remaining.compareTo(coin) >= 0 && count > 0) {
                remaining = remaining.subtract(coin);
                count--;
            }
        }
        return remaining.compareTo(BigDecimal.ZERO) == 0;
    }
}

What about extensibility?

Adding a maintenance mode is just a new MaintenanceState class that rejects all customer actions. Adding a new payment method (card, UPI) is a new state transition from idle, which shows how the State pattern keeps the machine extensible.

What should a senior engineer know?

  • Money handling must be exact with BigDecimal. Floating-point rounding in a rupee-based machine is a real bug.
  • The exact-change-only mode is a real-world constraint that naive designs ignore.
  • The State pattern eliminates illegal state combinations by construction.
  • In a real machine, the dispensing mechanism is asynchronous, so the DispensingState would wait for a hardware callback.

What are common interview follow-up questions?

  • How would you add a maintenance mode?
  • How do you handle the case where the machine cannot make change?
  • How would you add card payment support?
  • How do you prevent the machine from dispensing without sufficient payment?
  • What happens if the dispense mechanism jams?

Visual Overview

DIAGRAM
  Vending Machine State Machine:
  ──────────────────────────────

  ┌──────────┐  insertMoney  ┌─────────────┐
  │   IDLE   │──────────────→│  SELECTING   │
  └──────────┘               └──────┬───────┘
       ▲                            │ selectProduct
       │                            │ (stock OK + funds OK)
       │                            ▼
       │                     ┌──────────────┐
       │                     │  DISPENSING   │
       │                     └──────┬───────┘
       │                            │ dispense product
       │                            ▼
       │                     ┌──────────────────┐
       └─────────────────────│ RETURNING CHANGE │
           machine idle      └──────────────────┘
DIAGRAM
  Slot Inventory:
  ───────────────

  Slot A1: Passbook (Standard)  ₹30  qty: 5
  Slot A2: Cheque Book          ₹50  qty: 3
  Slot B1: Deposit Slip         ₹10  qty: 10

Key Takeaway

When you design a vending machine, reach straight for the State pattern, because a vending machine is fundamentally a finite state machine moving through idle, selecting, dispensing, and returning-change. Define a state interface with actions like insert money and select product, and a separate class for each state. The machine holds its current state and delegates every action to it, so each state permits only the transitions that make sense and rejects the rest.

This design matters because the naive alternative is a tangle of boolean flags like isDispensing and hasMoney, which inevitably produces illegal states. By making each phase its own class, the illegal transitions become impossible by construction. Adding a new phase like maintenance mode is just a new class.

The other entities are straightforward: a Product carries its price, a Slot holds a product and a count forming the inventory, and a money handler accumulates what the customer inserts. When the customer selects, the machine checks both that the slot has stock and that enough money is in before transitioning to dispensing, so the machine never gives away a product it does not have or has not been paid for.

Two things to always get right are exact money handling and change. Use BigDecimal throughout because a machine dealing in rupees cannot tolerate floating-point rounding, and compute change against available coin denominations, dropping into exact-change-only mode when the machine genuinely cannot make change. At National Bank of India, self-service kiosks dispensing cheque books and passbooks use exactly this design, where precise money handling and strictly valid state transitions are non-negotiable.

Remember one sentence: a vending machine is a State-pattern-driven finite state machine that checks stock and funds before dispensing, uses BigDecimal for exact money, and makes illegal transitions impossible by construction.

---

Share
Use ← → arrow keys to navigate