Problem: Design an ATM machine that handles user authentication, cash withdrawal, deposit, balance inquiry, and transaction history.
| Requirement | Details |
|---|
| Authentication | Card + PIN verification |
| Withdraw | Dispense cash if sufficient balance |
| Deposit | Accept cash/check, update balance |
| Balance inquiry | Show current account balance |
| Transaction history | Show recent transactions |
-cashDispenser: CashDispenser
+insertCard(card: Card): void
+enterPin(pin: string): void
+withdraw(amount: number): void
+deposit(amount: number): void
+insertCard(card: Card): void
+enterPin(pin: string): void
+withdraw(amount: number): void
+insertCard(card: Card): void
+enterPin(pin: string): void
class AuthenticatedState {
+withdraw(amount: number): void
+deposit(amount: number): void
-transactions: Transaction[]
+debit(amount: number): boolean
+credit(amount: number): void
+authenticate(cardNumber: string, pin: string): boolean
+getAccount(cardNumber: string): Account
+withdraw(accountNumber: string, amount: number): boolean
+dispense(amount: number): boolean
ATMState <|.. HasCardState
ATMState <|.. AuthenticatedState
| Pattern | Where | Why |
|---|
| State | ATM states (Idle, HasCard, Authenticated) | Clean state transitions |
| Singleton | ATM instance | One ATM machine |
| Facade | BankService | Simplifies complex bank operations |
enum TransactionType { WITHDRAWAL, DEPOSIT, BALANCE_CHECK }
private transactions: Transaction[] = [];
constructor(public accountNumber: string, private balance: number = 0) {}
debit(amount: number): boolean {
if (this.balance < amount) return false;
this.transactions.push(new Transaction(TransactionType.WITHDRAWAL, amount));
credit(amount: number): void {
this.transactions.push(new Transaction(TransactionType.DEPOSIT, amount));
getBalance(): number { return this.balance; }
insertCard(card: Card): void;
enterPin(pin: string): void;
withdraw(amount: number): void;
deposit(amount: number): void;
class IdleState implements ATMState {
constructor(private atm: ATM) {}
insertCard(card: Card): void { this.atm.setState(new HasCardState(this.atm)); }
enterPin(pin: string): void { throw new Error("Insert card first"); }
withdraw(amount: number): void { throw new Error("Authenticate first"); }
deposit(amount: number): void { throw new Error("Authenticate first"); }
private state: ATMState = new IdleState(this);
private currentAccount?: Account;
constructor(private bankService: BankService) {}
setState(state: ATMState): void { this.state = state; }
insertCard(card: Card): void { this.state.insertCard(card); }
enterPin(pin: string): void { this.state.enterPin(pin); }
withdraw(amount: number): void { this.state.withdraw(amount); }
- How would you handle running out of cash?
- How do you ensure transaction consistency?
- What happens if the machine loses power during a transaction?
- How would you add support for multiple currencies?