How does the cash dispensing algorithm work in your ATM design?
Fast recall
Quick revision
Core answer
Whether you can implement a classic greedy algorithm in a real-world constraint problem (limited bill inventory, exact change requirement)
Forgetting to check if exact change is possible before starting dispensing—you can't give back bills once started
Mention the edge case where greedy fails (e.g., have 3×$20 but need $50) and explain you'd need backtracking or reject the transaction if exact change isn't possible
Interview-ready guide
Interview answer
The cash dispenser keeps a map of each denomination to how many notes of it are in the machine. For a withdrawal, I'd use a greedy algorithm: start from the largest note and work down. For each denomination, I take the minimum of two things — how many I'd need, which is the remaining amount divided by the note value, and how many are actually in stock. I subtract that from the remaining amount and move to the next smaller note.
The detail that matters is the order of operations. I don't touch the real inventory while I'm calculating. I build a plan first — a small map of which notes and how many — and only after I've confirmed the remaining amount is exactly zero do I commit that plan to the inventory and dispense. If, after going through all denominations, the remaining isn't zero, then the machine simply can't make that exact amount with what it has, so I reject the withdrawal cleanly. The classic bug is decrementing inventory as you go, then discovering halfway that you can't complete the amount, which leaves the inventory in a corrupted state and the customer shortchanged.
A largest-first pass is a useful first candidate, but it is not a complete solver once note stock is limited. For example, taking one 50 can block a 60 withdrawal when three 20 notes were available. If the machine must find any feasible combination, I would add bounded backtracking or dynamic programming, then reserve and commit the chosen notes atomically.
How does the cash dispensing algorithm work in your ATM design
Direct Answer
The CashDispenser uses a greedy algorithm: it maintains a sorted map of denomination to count (e.g., 100→50 bills, 50→100 bills). For a withdrawal amount, iterate denominations from largest to smallest. For each denomination, dispense min(requestedAmount / denomination, availableCount) bills, subtract from both amount and inventory, repeat until amount reaches zero or no bills remain.
Deep Dive & Architecture
Validate before changing inventory
The dispenser receives an amount and a snapshot of available notes. It first rejects invalid amounts, then builds a note plan without changing the real inventory. A plan is valid only when the note counts do not exceed stock and their total equals the requested amount. Inventory is reduced only after the plan and the bank authorization both succeed.
Largest-first is a useful candidate, not a universal solver
A largest-first pass is fast and often gives a small number of notes, but limited inventory can make it miss a valid combination. Suppose the machine has one 50 note, three 20 notes, and no 10 notes. For 60, greedy takes 50 and fails, while three 20 notes work. A production design can use bounded backtracking or dynamic programming, or use a bank-approved dispensing policy whose note set and stock rules guarantee the simple algorithm.
Plan and commit form one protected operation
Two withdrawals must not reserve the same notes. In one process, the dispenser can lock around validation and commit. With shared or remote inventory, the final update needs a transaction or compare-and-set at the storage boundary. If physical dispensing fails after the bank debit, the workflow records the failure and starts the bank's reversal or reconciliation process rather than pretending the withdrawal completed.
Code Example
class CashDispenser {
// largest denomination first
private final TreeMap<Integer,Integer> inventory = new TreeMap<>(Comparator.reverseOrder());
// returns the bills to hand out, or empty if the amount can't be made
Optional<Map<Integer,Integer>> dispense(int amount) {
Map<Integer,Integer> plan = new LinkedHashMap<>();
int remaining = amount;
for (var e : inventory.entrySet()) {
int note = e.getKey(), available = e.getValue();
int need = Math.min(remaining / note, available); // greedy take
if (need > 0) { plan.put(note, need); remaining -= need * note; }
}
if (remaining != 0) return Optional.empty(); // cannot make exact amount
plan.forEach((note, cnt) -> inventory.merge(note, -cnt, Integer::sum)); // commit
return Optional.of(plan);
}
}
This is a fast candidate builder, not a complete combination search. Use it only with a validated note policy or add a bounded-search fallback before committing inventory.
Common Mistakes & Pitfalls
- Dispensing before checking feasibility — decrement inventory as you go, then discover you can't finish, leaving inventory corrupted. Build a plan, validate, then commit.
- Ignoring inventory limits —
amount / notewithoutmin(..., available)over-dispenses notes you don't have. - Assuming greedy always works — limited stock can defeat it even with familiar note values. Use a bounded search when finding any feasible combination is required.
- Not handling the impossible amount — return empty / reject, don't hand out the wrong total.
Check your understanding
Answer these without looking back at the article.
- Design an ATM system. What are the key classes and interactions?
- How do you model the State pattern for an ATM?
- What security considerations are important in your ATM design?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.