Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

👥10,104visitors

Built for engineers who want to go deep.


← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
Chaturmind
← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
HomeLearnSystem DesignLow-Level Design & Design PatternsMachine Coding Exercises
✓ FreeIntermediate· 8 min read

Vending Machine Design

The canonical State pattern interview question — modeling a vending machine as Idle/HasMoney/Dispensing/OutOfStock states, explicitly.

Published September 23, 2026


Vending Machine Design

If one machine-coding prompt exists specifically to test State Pattern, it's this one — a vending machine's behavior is entirely determined by which state it's in, making it close to the cleanest real-world example of the pattern.

Core classes

class Product {
    final String code;
    final double price;
    int quantity;
}

class Inventory {
    private final Map<String, Product> products = new HashMap<>();
    boolean hasStock(String code) { return products.containsKey(code) && products.get(code).quantity > 0; }
    void dispense(String code) { products.get(code).quantity--; }
}

interface CoinAcceptor { double insertCoin(Coin coin); } // returns running total inserted

Advertisement

Modeling as a state machine

IDLE ──(product selected)──▶ HAS_MONEY_PENDING (waiting for sufficient payment)
HAS_MONEY_PENDING ──(enough money inserted)──▶ DISPENSING
DISPENSING ──(product released)──▶ IDLE (or OUT_OF_STOCK if that was the last unit)
any state ──(selected product has zero stock)──▶ OUT_OF_STOCK (rejects selection, returns to IDLE)

Implementing with State pattern, explicitly

interface VendingMachineState {
    void selectProduct(VendingMachine machine, String code);
    void insertCoin(VendingMachine machine, Coin coin);
    void dispense(VendingMachine machine);
}

class IdleState implements VendingMachineState {
    public void selectProduct(VendingMachine machine, String code) {
        if (!machine.getInventory().hasStock(code)) {
            machine.setState(new OutOfStockState());
            return;
        }
        machine.setSelectedProduct(code);
        machine.setState(new HasMoneyPendingState());
    }
    public void insertCoin(VendingMachine machine, Coin coin) { /* no-op or reject — no product selected yet */ }
    public void dispense(VendingMachine machine) { /* no-op — nothing to dispense */ }
}

class HasMoneyPendingState implements VendingMachineState {
    public void selectProduct(VendingMachine machine, String code) { /* already mid-transaction — ignore or reject */ }
    public void insertCoin(VendingMachine machine, Coin coin) {
        double total = machine.addPayment(coin.getValue());
        if (total >= machine.getSelectedProductPrice()) {
            machine.setState(new DispensingState());
        }
    }
    public void dispense(VendingMachine machine) { /* not ready yet */ }
}

class DispensingState implements VendingMachineState {
    public void selectProduct(VendingMachine machine, String code) { /* mid-dispense — ignore */ }
    public void insertCoin(VendingMachine machine, Coin coin) { machine.refund(coin); } // reject further coins mid-dispense
    public void dispense(VendingMachine machine) {
        machine.getInventory().dispense(machine.getSelectedProduct());
        machine.returnChange();
        machine.setState(machine.getInventory().hasStock(machine.getSelectedProduct()) ? new IdleState() : new OutOfStockState());
    }
}

class OutOfStockState implements VendingMachineState {
    public void selectProduct(VendingMachine machine, String code) {
        if (machine.getInventory().hasStock(code)) machine.setState(new IdleState()); // a DIFFERENT product might still be in stock
    }
    public void insertCoin(VendingMachine machine, Coin coin) { machine.refund(coin); }
    public void dispense(VendingMachine machine) { /* nothing to dispense */ }
}
class VendingMachine {
    private VendingMachineState state = new IdleState();
    void setState(VendingMachineState state) { this.state = state; }
    void selectProduct(String code) { state.selectProduct(this, code); } // every public method delegates identically
    void insertCoin(Coin coin) { state.insertCoin(this, coin); }
    void dispenseProduct() { state.dispense(this); }
}

The reason this is the canonical State pattern question, rather than just an example: every one of VendingMachine's public methods has genuinely different, non-trivial behavior in every state (inserting a coin does nothing in IdleState, accumulates toward payment in HasMoneyPendingState, and triggers a refund in DispensingState/OutOfStockState) — this is exactly the profile State Pattern's own "when it earns its complexity" guidance describes, unlike the traffic-light example where an enum+switch would have been simpler.

Walking through one purchase

Tracing a single transaction through the states makes the design concrete. Say a cola costs ₹25, and the customer has a ₹20 and a ₹10 coin:

  1. IDLE. The customer presses A3 (cola). IdleState.selectProduct checks stock, records the selection, and moves to HAS_MONEY_PENDING. The display shows "Insert ₹25".
  2. HAS_MONEY_PENDING. They insert ₹20. The running total (20) is below the price, so the machine stays in this state and shows "₹5 more".
  3. They insert ₹10. The total (30) now covers the price, so the machine moves to DISPENSING.
  4. DISPENSING. The machine releases the cola, returns ₹5 change, clears the transaction, and goes back to IDLE (or OUT_OF_STOCK if that was the last cola).

Notice what didn't need an if: the same insertCoin call meant "accumulate" in step 2, and would mean "refund immediately" if a coin arrived during step 4. The current state object decides. That's the practical payoff of the pattern: each state class reads like a short description of how the machine behaves in that situation.

Two design improvements worth making

1. Money should never be double. 0.1 + 0.2 isn't exactly 0.3 in binary floating point, and a vending machine that compares totals to prices with >= will eventually reject exact payment or return the wrong change. Represent money in the smallest unit, as integers:

enum Coin {
    ONE(100), TWO(200), FIVE(500), TEN(1000), TWENTY(2000);    // values in paise
    final int paise;
    Coin(int paise) { this.paise = paise; }
}

final class Product {
    final String code; final String name; final int pricePaise;
    Product(String code, String name, int pricePaise) { this.code = code; this.name = name; this.pricePaise = pricePaise; }
}

2. "Out of stock" is usually a response, not a state. In the version above, selecting an empty product moves the whole machine into OutOfStockState, which then needs another selection to get out. From a user's point of view, the machine should simply say "A3 is sold out; choose another" and stay IDLE. Keep a machine-wide OutOfService state for cases where the whole machine can't sell (every slot empty, a cash-box fault, or maintenance mode):

class IdleState implements VendingMachineState {
    public void selectProduct(VendingMachine m, String code) {
        if (!m.inventory().hasStock(code)) {
            m.display("Sold out — please choose another item");   // stay IDLE
            return;
        }
        m.startTransaction(code);
        m.setState(new HasMoneyPendingState());
    }
    // ...
}

Cancel and refund

Customers change their minds. A cancel() event belongs on the state interface:

  • In HAS_MONEY_PENDING, it refunds everything inserted, and returns to IDLE.
  • In IDLE, it does nothing.
  • In DISPENSING, it's refused, because the product is already on its way.

Because each state implements cancel explicitly, "can the customer cancel now?" has one clear answer per state, rather than a flag checked in several places.

Making change with a limited coin supply

returnChange() hides the trickiest logic in the problem. The machine holds a finite number of each coin, so it may be unable to make change. A greedy approach (take the largest coin that fits, repeatedly) works for standard coin systems when every denomination is available. With limited counts, greedy can fail even when change is possible, so check before accepting payment:

Optional<Map<Coin, Integer>> makeChange(int amount, Map<Coin, Integer> available) {
    Map<Coin, Integer> result = new EnumMap<>(Coin.class);
    Coin[] coins = Coin.values();
    for (int i = coins.length - 1; i >= 0 && amount > 0; i--) {         // largest first
        Coin c = coins[i];
        int use = Math.min(amount / c.paise, available.getOrDefault(c, 0));
        if (use > 0) { result.put(c, use); amount -= use * c.paise; }
    }
    return amount == 0 ? Optional.of(result) : Optional.empty();       // empty → "exact change only"
}

If change can't be made, the machine should refuse the overpaying coin, or show "exact change only", before dispensing, never after taking the money. For guaranteed-optimal change with arbitrary coin sets and limited counts, a small dynamic program (like Coin Change, with counts) replaces the greedy loop without changing any state class.

Follow-up questions this topic invites — and their answers

Q: Why does OutOfStockState.selectProduct() check hasStock() for the newly-selected code, rather than just staying OutOfStock forever? A: Out-of-stock is per-product, not a global machine condition — a machine that's out of one item can still sell a different item, so the state needs to re-evaluate stock for whatever the new selection is, not assume the machine-wide state should persist.

Q: How would you handle a coin insertion that overshoots the product price? A: DispensingState (or a transition trigger just before it) needs to compute and dispense change — machine.returnChange() in the dispense() implementation is where that logic would live, calculating (total inserted - product price) and dispensing the appropriate coins/refusing if exact change isn't available.

Q: What's a concurrency concern for a real vending machine handling this design? A: A physical vending machine typically serves one transaction at a time by hardware nature, so concurrency is less of an issue than, say, Parking Lot's concurrent spot claims — but a software simulation or a networked/cashless variant would need the same kind of per-transaction locking discussed in Parking Lot — Implementation if multiple insertCoin/selectProduct calls could race.

Q: Could this same state machine structure model a different real-world system? A: Yes — any system where behavior is genuinely gated by a multi-step transaction with distinct in-progress states (a self-checkout kiosk, an elevator's door-open/moving states from Elevator System, an order's Pending/Paid/Shipped lifecycle) follows the identical State pattern shape.

Q: Why should the vending machine store money as integers rather than doubles? A: Binary floating point can't represent most decimal amounts exactly, so comparisons such as total >= price and change calculations can be off by tiny amounts, which leads to rejected exact payments or wrong change. Integer minor units (paise, cents) are exact.

Q: Should "out of stock" be a state of the machine? A: Only if the entire machine can't sell. A single sold-out product is better handled as a rejected selection that leaves the machine IDLE. A machine-level OutOfService state covers cases where every product is gone, or the machine is faulty or in maintenance.

Q: When should the machine check whether it can give change? A: Before completing the sale: ideally when a coin that would overpay is inserted, or at the latest before dispensing. If change isn't possible, refuse the coin or refund, instead of dispensing and keeping the customer's excess money.

Advertisement

Previous

Chess Engine Design

Next

ATM System Design

AI Tutor

Lesson: Vending Machine Design

Quick actions

AI responses can be inaccurate. Verify critical information.