🇮🇳
🇮🇳
Republic Day Special Offer!Get 20% OFF on all courses
Enroll Now
P
Prakalpana
📚Learn
Code Your Future
System Design⏱️ 18 min read📅 Jan 3

LLD: Design ATM Machine - Banking System OOP

RI
Rajesh IyerTech Lead at Goldman Sachs
📑 Contents (8 sections)

📌Problem Statement

Design an ATM machine system with authentication, transactions, and cash dispensing.

📌Requirements

  • Card authentication with PIN
  • Balance inquiry
  • Cash withdrawal
  • Cash deposit
  • Transfer between accounts
  • Transaction history
  • 📌Class Design

    ATM States

    public enum ATMState {
    IDLE,
    CARD_INSERTED,
    PIN_ENTERED,
    TRANSACTION_SELECTED,
    PROCESSING,
    DISPENSING_CASH
    }
    public interface ATMStateHandler {
    void insertCard(ATM atm, Card card);
    void enterPin(ATM atm, String pin);
    void selectTransaction(ATM atm, TransactionType type);
    void processTransaction(ATM atm, double amount);
    void ejectCard(ATM atm);
    }

    ATM Class

    public class ATM {
    private String atmId;
    private ATMState state;
    private Card currentCard;
    private Account currentAccount;
    private CashDispenser cashDispenser;
    private Bank bank;
    public ATM(String atmId, Bank bank) {
    this.atmId = atmId;
    this.bank = bank;
    this.state = ATMState.IDLE;
    this.cashDispenser = new CashDispenser();
    }
    public void insertCard(Card card) {
    if (state != ATMState.IDLE) {
    throw new InvalidStateException("ATM busy");
    }
    this.currentCard = card;
    this.state = ATMState.CARD_INSERTED;
    display("Enter PIN");
    }
    public boolean validatePin(String pin) {
    if (state != ATMState.CARD_INSERTED) {
    throw new InvalidStateException("Insert card first");
    }
    boolean valid = bank.validateCard(currentCard, pin);
    if (valid) {
    currentAccount = bank.getAccount(currentCard);
    state = ATMState.PIN_ENTERED;
    return true;
    }
    return false;
    }
    public double checkBalance() {
    validateSession();
    return currentAccount.getBalance();
    }
    public boolean withdraw(double amount) {
    validateSession();
    if (!cashDispenser.canDispense(amount)) {
    display("ATM has insufficient cash");
    return false;
    }
    if (currentAccount.getBalance() < amount) {
    display("Insufficient balance");
    return false;
    }
    state = ATMState.PROCESSING;
    boolean success = bank.debit(currentAccount, amount);
    if (success) {
    state = ATMState.DISPENSING_CASH;
    cashDispenser.dispense(amount);
    logTransaction(TransactionType.WITHDRAWAL, amount);
    return true;
    }
    return false;
    }
    public void ejectCard() {
    currentCard = null;
    currentAccount = null;
    state = ATMState.IDLE;
    }
    private void validateSession() {
    if (state != ATMState.PIN_ENTERED && state != ATMState.TRANSACTION_SELECTED) {
    throw new InvalidSessionException();
    }
    }
    }

    Cash Dispenser

    public class CashDispenser {
    private Map<Denomination, Integer> cashInventory;
    public CashDispenser() {
    cashInventory = new EnumMap<>(Denomination.class);
    cashInventory.put(Denomination.HUNDRED, 100);
    cashInventory.put(Denomination.FIVE_HUNDRED, 50);
    cashInventory.put(Denomination.TWO_THOUSAND, 20);
    }
    public boolean canDispense(double amount) {
    return getTotalCash() >= amount && amount % 100 == 0;
    }
    public Map<Denomination, Integer> dispense(double amount) {
    Map<Denomination, Integer> notes = new LinkedHashMap<>();
    double remaining = amount;
    for (Denomination denom : Denomination.values()) {
    int available = cashInventory.get(denom);
    int needed = (int) (remaining / denom.getValue());
    int toDispense = Math.min(available, needed);
    if (toDispense > 0) {
    notes.put(denom, toDispense);
    cashInventory.put(denom, available - toDispense);
    remaining -= toDispense * denom.getValue();
    }
    }
    return notes;
    }
    public void refill(Denomination denom, int count) {
    cashInventory.merge(denom, count, Integer::sum);
    }
    }
    public enum Denomination {
    TWO_THOUSAND(2000),
    FIVE_HUNDRED(500),
    HUNDRED(100);
    private final int value;
    Denomination(int value) { this.value = value; }
    public int getValue() { return value; }
    }

    Transaction

    public class Transaction {
    private String transactionId;
    private String accountId;
    private TransactionType type;
    private double amount;
    private LocalDateTime timestamp;
    private TransactionStatus status;
    public Transaction(String accountId, TransactionType type, double amount) {
    this.transactionId = UUID.randomUUID().toString();
    this.accountId = accountId;
    this.type = type;
    this.amount = amount;
    this.timestamp = LocalDateTime.now();
    this.status = TransactionStatus.PENDING;
    }
    }

    📌Design Patterns

  • 1State Pattern: ATM states
  • 2Strategy Pattern: Cash dispensing algorithm
  • 3Singleton: ATM controller
  • Asked at Goldman Sachs, JP Morgan, and PayPal interviews.

    RI

    Written by

    Rajesh Iyer

    Tech Lead at Goldman Sachs

    🚀 Master System Design

    Join 500+ developers

    Explore Courses →
    Chat on WhatsApp