LLD Problem
Design ATM Machine
Design an ATM system with card authentication, cash withdrawal, deposits, balance inquiry, and cash dispenser logic.
25 min readMultiple Patterns
1Requirements
Functional
- Insert card and authenticate with PIN
- Check balance
- Withdraw cash
- Deposit cash/check
- Transfer between accounts
- Print receipt
Non-Functional
- Secure PIN validation
- Transaction atomicity
- Cash dispenser optimization
- Daily withdrawal limits
- Card retention after 3 wrong PINs
- Session timeout
2Design Patterns
State Pattern
ATM states: Idle, CardInserted, Authenticated, Transaction, etc.
Chain of Responsibility
Cash dispenser: $100 → $50 → $20 → $10 notes.
Strategy Pattern
Different transaction types: Withdraw, Deposit, Transfer.
Command Pattern
Encapsulate transactions for logging and undo.
3Class Design
ATM
- state
- cashDispenser
- cardReader
- bank
+ insertCard()
+ enterPIN()
+ withdraw()
+ deposit()
ATMState
- Interface
+ insertCard()
+ enterPIN()
+ selectTransaction()
+ executeTransaction()
CashDispenser
- cashSlots[]
+ dispense()
+ canDispense()
+ addCash()
CashSlot
- denomination
- count
+ dispense()
+ setNext()
Account
- number
- balance
- type
+ withdraw()
+ deposit()
+ getBalance()
Transaction
- id
- type
- amount
- timestamp
+ execute()
+ log()
4Cash Dispenser Implementation
The cash dispenser uses Chain of Responsibility: Start with largest denomination ($100), use as many as possible, pass remaining amount to next smaller denomination ($50, $20, $10).
Cash Dispenser with Chain of Responsibility
// ========== Cash Slot (Chain Handler) ==========
abstract class CashSlot {
protected int denomination;
protected int count;
protected CashSlot next;
public CashSlot(int denomination, int count) {
this.denomination = denomination;
this.count = count;
}
public void setNext(CashSlot next) {
this.next = next;
}
public void dispense(int amount, List<Integer> result) {
if (amount >= denomination) {
int notesNeeded = amount / denomination;
int notesToDispense = Math.min(notesNeeded, count);
if (notesToDispense > 0) {
result.add(notesToDispense);
System.out.println("Dispensing " + notesToDispense + " x $" + denomination);
count -= notesToDispense;
amount -= notesToDispense * denomination;
}
}
if (next != null && amount > 0) {
next.dispense(amount, result);
} else if (amount > 0) {
System.out.println("Cannot dispense remaining: $" + amount);
}
}
public boolean canDispense(int amount) {
int remaining = amount;
CashSlot current = this;
while (current != null && remaining > 0) {
int notes = Math.min(remaining / current.denomination, current.count);
remaining -= notes * current.denomination;
current = current.next;
}
return remaining == 0;
}
}
class HundredDollarSlot extends CashSlot {
public HundredDollarSlot(int count) { super(100, count); }
}
class FiftyDollarSlot extends CashSlot {
public FiftyDollarSlot(int count) { super(50, count); }
}
class TwentyDollarSlot extends CashSlot {
public TwentyDollarSlot(int count) { super(20, count); }
}
class TenDollarSlot extends CashSlot {
public TenDollarSlot(int count) { super(10, count); }
}
// ========== Cash Dispenser ==========
class CashDispenser {
private CashSlot chain;
public CashDispenser() {
// Build the chain
CashSlot hundred = new HundredDollarSlot(10);
CashSlot fifty = new FiftyDollarSlot(20);
CashSlot twenty = new TwentyDollarSlot(50);
CashSlot ten = new TenDollarSlot(100);
hundred.setNext(fifty);
fifty.setNext(twenty);
twenty.setNext(ten);
this.chain = hundred;
}
public boolean canDispense(int amount) {
if (amount % 10 != 0) return false;
return chain.canDispense(amount);
}
public List<Integer> dispense(int amount) {
List<Integer> dispensed = new ArrayList<>();
if (canDispense(amount)) {
chain.dispense(amount, dispensed);
}
return dispensed;
}
}
// ========== Usage ==========
public class Main {
public static void main(String[] args) {
CashDispenser dispenser = new CashDispenser();
System.out.println("Withdraw $280:");
dispenser.dispense(280);
// Output: 2 x $100, 1 x $50, 1 x $20, 1 x $10
System.out.println("\nCan dispense $15? " + dispenser.canDispense(15));
// Output: false (not multiple of 10)
}
}5Interview Follow-up Questions
Interview Follow-up Questions
Common follow-up questions interviewers ask
6Key Takeaways
1State Pattern for ATM states (Idle, CardInserted, etc.).
2Chain of Responsibility for cash dispenser.
3Strategy for different transaction types.
4Always validate before dispensing cash.
5Handle concurrency with optimistic locking.
6Consider edge cases: timeout, card retention, denomination limits.