Back to LLD explorer
State / Concurrency• Patterns: State, Chain of Responsibility

ATM Machine

Hard

Problem Summary

Design a secure Automated Teller Machine (ATM) controller managing card insertion, PIN validation, state workflows, multi-denomination cash dispensing, and two-phase ledger commit.

Functional Scope

  • Hardware state cycle: IdleState -> CardInsertedState -> PinEnteredState -> DispensingState.
  • Card authentication & PIN verification with 3-attempt lockout.
  • Chain of Responsibility cash dispenser handling $100, $50, and $20 currency notes.
  • Two-phase commit between physical cash dispenser and core banking host.
  • Fault tolerance against power cuts or note dispenser jams.

Entity-Relationship (ER) Schema

ATM [1] --> [1] ATMState
ATM [1] --> [1] CashDispenser
CashDispenser [1] --> [0..1] CashDispenser (Next in Chain)
ATMState <|.. IdleState
ATMState <|.. CardInsertedState
ATMState <|.. PinEnteredState

Design Approach

Apply the Chain of Responsibility pattern for multi-denomination cash dispensation, coupled with the State pattern to enforce strict sequential validation of ATM operations.

Core Classes & Models

ATM (Context managing active card, user balance, and hardware modules)ATMState (Interface with insertCard, enterPin, selectOption, withdraw)IdleState, CardInsertedState, PinEnteredState, DispensingStateCashDispenser (Abstract chain link with note denomination and cassette count)HundredDispenser, FiftyDispenser, TwentyDispenser (Concrete note dispensers)
Code Blueprint
98 lines
/**
 * Low-Level Design: ATM Machine
 * Design Patterns Applied:
 *  - State Pattern: Operational lifecycle states (CardInserted, PinEntered, Dispensing)
 *  - Chain of Responsibility Pattern: Cash dispensing across denominations ($100, $50, $20)
 */

import java.util.*;

// --- [DESIGN PATTERN: State] State interface ---
interface ATMState {
    void insertCard(ATM atm);
    void enterPin(ATM atm, int pin);
    void withdrawCash(ATM atm, int amount);
}

class IdleState implements ATMState {
    @Override public void insertCard(ATM atm) {
        System.out.println("💳 Card inserted. Please enter PIN.");
        atm.setState(new HasCardState());
    }
    @Override public void enterPin(ATM atm, int pin) { System.out.println("Insert card first."); }
    @Override public void withdrawCash(ATM atm, int amount) { System.out.println("Insert card first."); }
}

class HasCardState implements ATMState {
    @Override public void insertCard(ATM atm) { System.out.println("Card already in slot."); }
    @Override public void enterPin(ATM atm, int pin) {
        if (pin == 1234) {
            System.out.println("✅ PIN Verified.");
            atm.setState(new AuthenticatedState());
        } else {
            System.out.println("❌ Wrong PIN.");
        }
    }
    @Override public void withdrawCash(ATM atm, int amount) { System.out.println("Enter PIN first."); }
}

class AuthenticatedState implements ATMState {
    @Override public void insertCard(ATM atm) { System.out.println("Card already inserted."); }
    @Override public void enterPin(ATM atm, int pin) { System.out.println("Already authenticated."); }
    @Override public void withdrawCash(ATM atm, int amount) {
        atm.dispense(amount);
        atm.setState(new IdleState());
    }
}

// --- [DESIGN PATTERN: Chain of Responsibility] Dispenser chain ---
abstract class CashDispenser {
    protected CashDispenser next;
    protected final int denomination;
    public CashDispenser(int denomination) { this.denomination = denomination; }
    public void setNext(CashDispenser next) { this.next = next; }

    public void dispense(int amount) {
        int notes = amount / denomination;
        int rem = amount % denomination;
        if (notes > 0) {
            System.out.println("💵 Dispensed " + notes + " x $" + denomination);
        }
        if (rem > 0 && next != null) {
            next.dispense(rem);
        }
    }
}

class Dispenser100 extends CashDispenser { public Dispenser100() { super(100); } }
class Dispenser50 extends CashDispenser { public Dispenser50() { super(50); } }
class Dispenser20 extends CashDispenser { public Dispenser20() { super(20); } }

class ATM {
    private ATMState state = new IdleState();
    private final CashDispenser cashChain;

    public ATM() {
        Dispenser100 d100 = new Dispenser100();
        Dispenser50 d50 = new Dispenser50();
        Dispenser20 d20 = new Dispenser20();
        d100.setNext(d50);
        d50.setNext(d20);
        this.cashChain = d100;
    }

    public void setState(ATMState state) { this.state = state; }
    public void insertCard() { state.insertCard(this); }
    public void enterPin(int pin) { state.enterPin(this, pin); }
    public void withdraw(int amount) { state.withdrawCash(this, amount); }
    public void dispense(int amount) { cashChain.dispense(amount); }
}

public class Main {
    public static void main(String[] args) {
        ATM atm = new ATM();
        atm.insertCard();
        atm.enterPin(1234);
        atm.withdraw(270);
    }
}