Command Palette

Search for a command to run...

PHASE 4Intermediate ~8 min· topic 3 of 18Level 1

Problem 4.3 — ATM

In one line

State + transaction handling + cash management + authentication. Districts it from vending machine with authentication, session and ledger concerns.

0/18 · 0%

Key ideas

  1. 01

    In plain words: an ATM is a vending machine that dispenses cash instead of chips, but with two extra headaches — it must check who you are, and it must never hand out money it doesn't have or let your balance go negative.

  2. 02

    States: Idle, Authenticated, Withdraw, Dispensing, OutOfService.

  3. 03

    Authentication: card + PIN; 3 attempts lock — keep a session, not a global 'logged in' flag.

  4. 04

    Cash management: denomination counts (100s/500s/2000s); withdrawal must respect available denominations.

  5. 05

    Ledger: every withdrawal debits the account record and decrements cash — both must be atomic (business + machine consistency).

  6. 06

    Transaction history per card; failure modes (insufficient funds, machine out of cash, network timeout) each handled explicitly.

  7. 07

    Concurrency: two ATMs on one account — the account balance check/update must be atomic (DB-level, not a boolean).

  8. 08

    Interview differentiator: prioritize core flow (auth → withdraw → dispense), then replay/rollback on network failure.

Java / Spring map

  • →

    Map<Denomination, Integer> cash; a WithdrawService that is @Transactional on the account row.

Code & diagrams

AtmWithdraw.javajava

Greedy denomination dispense + the two-invariant guard (no overdraft, no fake cash) in one method.

public enum Denomination { TWO_THOUSAND(2000), FIVE_HUNDRED(500), HUNDRED(100);
  final int value;
  Denomination(int v) { this.value = v; }
}

public class CashTray {
  private final Map<Denomination, Integer> counts = new EnumMap<>(Denomination.class);

  public CashTray(Map<Denomination, Integer> initial) { counts.putAll(initial); }

  // Greedy from largest to smallest. Returns null if the exact amount can't be made.
  public synchronized Map<Denomination, Integer> withdraw(int amount) {
    if (amount % 100 != 0) throw new IllegalArgumentException("multiples of 100 only");
    Map<Denomination, Integer> plan = new EnumMap<>(Denomination.class);
    int remaining = amount;
    for (Denomination d : Denomination.values()) {
      int available = counts.getOrDefault(d, 0);
      int use = Math.min(available, remaining / d.value);
      if (use > 0) { plan.put(d, use); remaining -= use * d.value; }
    }
    if (remaining != 0) return null;              // machine physically cannot make this amount
    plan.forEach((d, n) -> counts.merge(d, -n, Integer::sum));   // commit only if the whole plan works
    return plan;
  }
}

public class WithdrawService {
  private final AccountRepository accounts;   // @Transactional boundary lives here
  private final CashTray tray;

  public WithdrawResult withdraw(String accountId, int amount) {
    // INVARIANT 1: no overdraft — checked and debited atomically at the DB row
    boolean debited = accounts.debitIfSufficientFunds(accountId, amount);  // UPDATE ... WHERE balance >= amount
    if (!debited) return WithdrawResult.insufficientFunds();

    // INVARIANT 2: no fake cash — the tray can refuse even after the ledger says yes
    Map<Denomination, Integer> notes = tray.withdraw(amount);
    if (notes == null) {
      accounts.credit(accountId, amount);      // compensate: give the money back
      return WithdrawResult.cannotDispenseExactAmount();
    }
    return WithdrawResult.success(notes);
  }
}

Explain without notes

01

What is exactly 'atomic' about an ATM withdrawal — which two things must succeed together?

Practice

01

Implement withdraw with greedy denomination selection + 'exact amount unavailable' error.

Trade-offs

  • ↔

    Safety over speed: cash is a hard limit; never trust an in-memory balance across processes.

Completion checklist

  • I can state the two invariants an ATM must never break (no overdraft, no fake cash), and show the compensating action when the second check fails after the first succeeds.

Back to phase