Machine Coding Problem

ATM Machine

maco30maco60macoAllfintechchain-of-responsibility-&-state-pattern
Commonly Asked By:GoogleMicrosoftJPMorgan Chase

Functional Scope (In-Scope)

  • State-Driven Terminal Control: Support distinct step flows (Idle, CardInserted, PINVerified, Dispensing) using structured State Pattern classes.
  • Greedy Cash Dispensation: Distribute banknotes dynamically matching high-to-low greedy note alignments using a Chain of Responsibility pattern.
  • Atomic Account Debiting: Prevent partial payout errors by locking cash and debiting accounts atomically.
  • Fail-Safe Banknote Rollbacks: On failure to dispense requested note patterns, roll back balance subtractions instantly.

Explicit Boundaries (Out-of-Scope)

  • No Physical Mechanical Hardware Control: Rollout ignores thermal slip printer APIs, magnetic strip reads, or physical deposit vaults.
  • No Central Network Routing Broker: Assumes direct secure loop connections to bank accounts.

Practical reference designs showing ATM state control and transaction safety in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

interface ATMState {
    void insertCard(ATMMachine atm, String accountNumber);
    void enterPin(ATMMachine atm, String pin);
    void withdraw(ATMMachine atm, int amount);
    void cancel(ATMMachine atm);
}

class IdleState implements ATMState {
    @Override
    public void insertCard(ATMMachine atm, String accountNumber) {
        Account acc = atm.getAccount(accountNumber);
        if (acc != null) {
            atm.setActiveAccount(acc);
            atm.setState(new CardInsertedState());
            System.out.println("[ATM State] Card inserted successfully for: " + accountNumber);
        } else {
            System.out.println("[ATM State] Invalid Card: Account not found.");
        }
    }
    @Override public void enterPin(ATMMachine atm, String pin) { System.out.println("[ATM ERROR] Please insert card first."); }
    @Override public void withdraw(ATMMachine atm, int amount) { System.out.println("[ATM ERROR] Please insert card first."); }
    @Override public void cancel(ATMMachine atm) { System.out.println("[ATM ERROR] No active session to cancel."); }
}

class CardInsertedState implements ATMState {
    @Override public void insertCard(ATMMachine atm, String accountNumber) { System.out.println("[ATM ERROR] Card already inside."); }
    @Override
    public void enterPin(ATMMachine atm, String pin) {
        if (atm.getActiveAccount().verifyPin(pin)) {
            atm.setState(new PinVerifiedState());
            System.out.println("[ATM State] PIN Verified. Welcome back!");
        } else {
            System.out.println("[ATM State] Incorrect PIN. Session terminated.");
            atm.cancel();
        }
    }
    @Override public void withdraw(ATMMachine atm, int amount) { System.out.println("[ATM ERROR] Please enter PIN first."); }
    @Override
    public void cancel(ATMMachine atm) {
        System.out.println("[ATM State] Session cancelled. Card returned.");
        atm.setActiveAccount(null);
        atm.setState(new IdleState());
    }
}

class PinVerifiedState implements ATMState {
    @Override public void insertCard(ATMMachine atm, String accountNumber) { System.out.println("[ATM ERROR] Card already inside."); }
    @Override public void enterPin(ATMMachine atm, String pin) { System.out.println("[ATM ERROR] PIN already verified."); }
    @Override
    public void withdraw(ATMMachine atm, int amount) {
        atm.setState(new DispensingState());
        System.out.println("[ATM State] Processing withdrawal of $" + amount + "...");
        atm.dispenseCash(amount);
    }
    @Override
    public void cancel(ATMMachine atm) {
        System.out.println("[ATM State] Ejecting card. Session completed.");
        atm.setActiveAccount(null);
        atm.setState(new IdleState());
    }
}

class DispensingState implements ATMState {
    @Override public void insertCard(ATMMachine atm, String accNum) { System.out.println("[ATM ERROR] Please wait, currently dispensing."); }
    @Override public void enterPin(ATMMachine atm, String pin) { System.out.println("[ATM ERROR] Please wait, currently dispensing."); }
    @Override public void withdraw(ATMMachine atm, int amount) { System.out.println("[ATM ERROR] Please wait, currently dispensing."); }
    @Override public void cancel(ATMMachine atm) { System.out.println("[ATM ERROR] Cannot cancel while dispensing cash."); }
}

interface CashDispenseChain {
    void setNext(CashDispenseChain next);
    boolean dispense(int amount, Map<Integer, Integer> dispensedNotes, Map<Integer, Integer> inventory);
}

class NoteDispenser implements CashDispenseChain {
    private final int denomination;
    private CashDispenseChain next;

    public NoteDispenser(int denomination) {
        this.denomination = denomination;
    }

    @Override
    public void setNext(CashDispenseChain next) {
        this.next = next;
    }

    @Override
    public boolean dispense(int amount, Map<Integer, Integer> dispensedNotes, Map<Integer, Integer> inventory) {
        if (amount <= 0) return true;
        int available = inventory.getOrDefault(denomination, 0);
        int needed = amount / denomination;
        int actual = Math.min(needed, available);

        if (actual > 0) {
            dispensedNotes.put(denomination, actual);
            amount -= actual * denomination;
        }

        if (amount > 0) {
            if (next != null) {
                return next.dispense(amount, dispensedNotes, inventory);
            }
            return false;
        }
        return true;
    }
}

class CashDispenser {
    private final Map<Integer, Integer> noteInventory = new ConcurrentHashMap<>();
    private final CashDispenseChain chain;
    private final ReentrantLock lock = new ReentrantLock();

    public CashDispenser() {
        noteInventory.put(100, 20); // $2000
        noteInventory.put(50, 40);  // $2000
        noteInventory.put(20, 100); // $2000

        CashDispenseChain c100 = new NoteDispenser(100);
        CashDispenseChain c50 = new NoteDispenser(50);
        CashDispenseChain c20 = new NoteDispenser(20);

        c100.setNext(c50);
        c50.setNext(c20);
        this.chain = c100;
    }

    public boolean dispense(int amount) {
        lock.lock();
        try {
            Map<Integer, Integer> dispensed = new HashMap<>();
            if (chain.dispense(amount, dispensed, noteInventory)) {
                for (Map.Entry<Integer, Integer> entry : dispensed.entrySet()) {
                    noteInventory.put(entry.getKey(), noteInventory.get(entry.getKey()) - entry.getValue());
                }
                System.out.println("[Dispenser] Dispensed bills: " + dispensed);
                return true;
            }
            System.out.println("[Dispenser] Cannot dispense requested amount with current bill combinations!");
            return false;
        } finally {
            lock.unlock();
        }
    }
}

class Account {
    private final String accountNumber;
    private final String pin;
    private double balance;
    private final ReentrantLock lock = new ReentrantLock();

    public Account(String accountNumber, String pin, double initialBalance) {
        this.accountNumber = accountNumber;
        this.pin = pin;
        this.balance = initialBalance;
    }

    public String getAccountNumber() { return accountNumber; }
    public boolean verifyPin(String enteredPin) { return pin.equals(enteredPin); }
    
    public double getBalance() {
        lock.lock();
        try { return balance; } finally { lock.unlock(); }
    }

    public boolean debit(double amount) {
        lock.lock();
        try {
            if (balance >= amount) {
                balance -= amount;
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    public void credit(double amount) {
        lock.lock();
        try {
            balance += amount;
        } finally {
            lock.unlock();
        }
    }
}

class ATMMachine {
    private ATMState state = new IdleState();
    private Account activeAccount = null;
    private final CashDispenser dispenser = new CashDispenser();
    private final Map<String, Account> accounts = new ConcurrentHashMap<>();
    private final ReentrantLock atmLock = new ReentrantLock();

    public void registerAccount(Account acc) {
        accounts.put(acc.getAccountNumber(), acc);
    }

    public Account getAccount(String accountNumber) {
        return accounts.get(accountNumber);
    }

    public void setState(ATMState state) {
        this.state = state;
    }

    public void setActiveAccount(Account acc) {
        this.activeAccount = acc;
    }

    public Account getActiveAccount() {
        return activeAccount;
    }

    public void insertCard(String accountNumber) {
        atmLock.lock();
        try { state.insertCard(this, accountNumber); } finally { atmLock.unlock(); }
    }

    public void enterPin(String pin) {
        atmLock.lock();
        try { state.enterPin(this, pin); } finally { atmLock.unlock(); }
    }

    public void withdraw(int amount) {
        atmLock.lock();
        try { state.withdraw(this, amount); } finally { atmLock.unlock(); }
    }

    public void cancel() {
        atmLock.lock();
        try { state.cancel(this); } finally { atmLock.unlock(); }
    }

    public void dispenseCash(int amount) {
        if (activeAccount == null) return;
        if (activeAccount.debit(amount)) {
            if (dispenser.dispense(amount)) {
                System.out.println("[ATM Success] Completed withdraw of $" + amount + ". Ejecting card.");
                state = new IdleState();
                activeAccount = null;
            } else {
                System.out.println("[ATM Error] Dispensation error. Restoring account balance.");
                activeAccount.credit(amount);
                state = new PinVerifiedState();
            }
        } else {
            System.out.println("[ATM Error] Insufficient account balance.");
            state = new PinVerifiedState();
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== ATM CONCURRENT SIMULATION DRIVER ===");
        ATMMachine atm = new ATMMachine();
        Account acc = new Account("1234-5678", "4321", 500.00);
        atm.registerAccount(acc);

        System.out.println("Initial Balance: $" + acc.getBalance());
        
        atm.insertCard("1234-5678");
        atm.enterPin("4321");
        atm.withdraw(280); // Should dispense 2x$100, 1x$50, 1x$20 = $270 + $10 fails if denomination is 20/50/100 only! Wait, 280: 2x100 + 1x50 + 1x20 = 270 (10 left - fail)
        
        // Let's retry with a multiple of 20/50/100
        atm.withdraw(290); // 2x100, 1x50, 2x20 = 290! Dispenses successfully.
        
        System.out.println("Final Account Balance: $" + acc.getBalance());
    }
}