Designing an ATM: the object-oriented design interview, explained
"Design an ATM" looks friendly and turns sharp the moment you start, because an ATM is a state machine wearing a metal shell. The same button means different things depending on where you are: pressing "withdraw" before a card is inserted is nonsense, pressing it after you have authenticated is the whole point. That is what an object-oriented design interview is probing — not whether you can list the parts of a cash machine, but whether you can model a flow where legal actions depend on the current state, decide where each responsibility lives, and keep the design honest when money is on the line. There is no algorithm to crack here; the entire round is the quality of your object model.
This guide walks the ATM the way a strong candidate works it in an OOD / LLD interview: clarify the requirements, identify the core entities, sketch the classes and the session flow, and then defend the two decisions the interviewer always pushes on — modelling the session with the State pattern so each step only allows what it should, and dispensing cash with a Chain of Responsibility across note denominations — plus the edge case that separates seniors from juniors: what happens when the account is debited and then the dispenser jams. In a low-level design round you write these classes out in an editor, not as a finished program but as a design you can explain and defend out loud, one method at a time.
Why the ATM comes up in object-oriented design interviews
The ATM earns its place because it forces a flow-with-states in a domain nobody needs explained. There are obvious entities (a machine, a card, an account, a cash dispenser), an obvious sequence (insert card, enter PIN, pick an operation, take the cash), obvious variation (withdraw, deposit, balance, transfer), and an obvious place for things to go badly wrong (money leaves the account but not the slot). An interviewer can watch you make a dozen modelling decisions in forty-five minutes and see exactly how you reason about state, responsibility and failure.
The interviewer rarely lets a tidy diagram stand on its own. "A customer asks to withdraw 200. You check the balance, it's fine, you debit the account — and then the cash dispenser jams and no notes come out. Walk me through exactly what state your ATM is in now, what the customer's balance is, and what you'd change so a debited customer never walks away empty-handed." That question — transaction atomicity across a debit and a physical dispense — is the one a pretty state diagram can never survive, and the one the ATM is really testing once the basics are on the board.
The rounds on Zynter are modelled on how the loop runs, not scraped from any candidate's recall. What matters for prep is the shape: you put a class or a state on the board, and the next sentence out of the interviewer's mouth attacks a seam — an action taken in the wrong state, a denomination the machine is out of, or a debit with no cash behind it.
What the interviewer is really testing
An object-oriented design answer is read on the same four dimensions as any design round. Problem solving — did you model the session as states so illegal actions are impossible by construction, or scatter if (status == ...) checks everywhere? Communication — can you justify why the cash dispenser is a chain of handlers rather than one big loop? Technical execution — do you handle the real failure (debit then dispenser jam, wrong PIN, out of a denomination), or only the happy path? Time management — did you clarify and prioritise before coding, and spend your time on the state model and the money-safety story rather than perfecting the keypad class? Reading this guide teaches the design; it does not teach you to defend it while someone says "now the dispenser jammed mid-withdrawal."
Step 1: Clarify the requirements
The biggest mistake in an LLD round is writing a class before asking a question. Spend the first two minutes scoping out loud — it is graded, and it stops you building the wrong machine.
Functional requirements to confirm:
- The user inserts a card and enters a PIN, which is authenticated against the bank.
- The user selects an operation: withdraw, deposit, balance inquiry, or transfer.
- For a withdrawal, the machine checks the account balance and its own cash availability, dispenses notes, and updates the balance.
- The machine ejects the card and returns to idle; the user can cancel at most steps.
- Errors to handle: a wrong PIN (with a retry limit), insufficient funds, and the machine being out of cash or out of a particular denomination.
Non-functional / constraints to surface (this is where senior candidates separate themselves):
- State-dependent behaviour — the valid actions depend entirely on where you are in the session, so the design should make illegal actions in a state impossible rather than relying on scattered checks.
- Money safety — a debit and a physical dispense must not be able to disagree; the design must say what happens when one succeeds and the other fails.
- Scope boundaries — are we handling deposits and envelopes, transfers, receipts, multi-currency? Agree what is in and out, out loud, so you are not marked down for "missing" something you deliberately deferred.
State your assumptions explicitly: "I'll assume card-plus-PIN auth against a bank service I'll stub, withdrawals and balance in scope, deposits noted but deferred — tell me if you want transfers too." That one sentence shows you prioritise, which is itself part of the score.
Step 2: Identify the core entities
With requirements agreed, name the classes and — because this problem is a state machine — the states before writing any. The nouns and the steps are your first draft of the object model:
-
ATM— the central object; holds the current state, the cash inventory, and references to the screen, keypad, card reader and dispenser. It delegates every user action to its current state. -
The states —
IdleState(waiting for a card) →CardInsertedState(awaiting PIN) →AuthenticatedState/SelectOperationState(choosing what to do) →TransactionState(performing it) → back toIdleState. Each is a class implementing a commonATMStateinterface. -
Card— the card number and the account it maps to. -
Account— balance and the operations the bank performs on it (debit, credit). -
BankService— a stubbed backend: authenticate a card and PIN, and apply debits and credits to accounts. -
CashDispenser— dispenses a requested amount as notes; modelled as a chain of denomination handlers. -
Transaction— an abstract operation withWithdrawTransaction,DepositTransaction,BalanceInquiryandTransferTransactionsubclasses. -
Screen/Keypad— the input and output surfaces, usually stubbed.
Naming the relationships out loud matters as much as the classes: an ATM has one current ATMState and swaps it as the session advances; a Card maps to an Account; the ATM delegates authentication and balance changes to the BankService; and the CashDispenser is a chain of denomination handlers. That "has-a / delegates-to" vocabulary is what an interviewer listens for.
Step 3: Sketch the classes
Now write the classes — as skeletons you can talk through, not a finished program. Lead with the ATMState interface and the dispenser chain, because they carry the two design decisions.
interface ATMState {
void insertCard(ATM atm, Card card);
void enterPin(ATM atm, String pin);
void selectOperation(ATM atm, OperationType op, int amount);
void cancel(ATM atm);
// illegal actions in a given state throw / are no-ops
}
class IdleState implements ATMState {
public void insertCard(ATM atm, Card card) {
atm.setCurrentCard(card);
atm.setState(new CardInsertedState()); // transition
}
public void enterPin(ATM atm, String pin) {
throw new IllegalStateException("insert a card first");
}
// selectOperation / cancel: nothing to do while idle
}
class ATM {
private ATMState state = new IdleState();
private CashDispenser dispenser;
private BankService bank;
public void setState(ATMState s) { this.state = s; }
// every user action is delegated to the current state:
public void insertCard(Card c) { state.insertCard(this, c); }
public void enterPin(String pin) { state.enterPin(this, pin); }
public void selectOperation(OperationType op, int amt) {
state.selectOperation(this, op, amt);
}
}
// Cash dispensing as a Chain of Responsibility over denominations:
abstract class CashDispenser {
protected CashDispenser next;
public void setNext(CashDispenser n) { this.next = n; }
public abstract void dispense(int amount);
}
class NoteDispenser extends CashDispenser {
private final int denom; // e.g. 500, 100, 50
private int count; // notes of this denom in the machine
public void dispense(int amount) {
int num = Math.min(amount / denom, count);
if (num > 0) { count -= num; /* release num notes */ }
int remaining = amount - num * denom;
if (remaining > 0 && next != null) next.dispense(remaining);
else if (remaining > 0) throw new InsufficientCashException();
}
}
Talk through the choices as you write them: the ATM holds no if (status == ...) ladder — it delegates every action to state, and each state knows what it allows and what transition follows, so an illegal action in a state is impossible by construction; and the dispenser is a chain where each denomination handles as much as it can and passes the remainder down, which is why dispensing 2000 naturally becomes "three 500s, then five 100s" without a tangle of nested loops.
Step 4: The design decisions interviewers probe
The diagram is the easy half. The follow-ups live in a handful of decisions, and having an answer ready for each is what the round rewards.
-
The State pattern for the session — model each step as a state class implementing
ATMState; theATMdelegates to its current state, and each state permits only its legal actions and performs the transition. Say why this beats a status enum with scatteredifchecks: new steps are new classes (open/closed), the legal actions are localised, and you cannot accidentally run a withdrawal before authentication becauseIdleStatesimply does not offer it. - Chain of Responsibility for the dispenser — to hand out an amount, chain denomination handlers from largest to smallest (500 → 100 → 50); each dispenses as many of its note as it can and passes the remainder on, and a leftover at the end means the amount cannot be made. Name the alternative (one method with nested loops) and why the chain is cleaner and easier to extend with a new denomination.
- Transaction atomicity — the debit-then-jam problem — this is the follow-up from the opening scenario, and the one most candidates miss. If you debit the account and then the dispenser jams, the customer has lost money for cash they never got. The fixes to name: debit only after a successful dispense (reserve/authorise, dispense, then commit the debit), or wrap the debit and dispense so a dispense failure triggers a compensating credit to roll the balance back. The key insight to state: a logical debit and a physical dispense can each fail independently, so the design must order them, or compensate, so the two can never disagree.
-
Wrong-PIN handling and security — authenticate through the
BankService, cap retries (for example three) and then retain or eject the card; never store the PIN on the ATM. - Edge cases — out of cash entirely, out of one denomination (so 2000 is impossible even though the total is available), a cancelled session mid-transaction, and a card left in the machine. You will not design all of them; naming them shows you see past the happy path.
Low-Level Design
LLD · ATM
Design the ATM and defend the debit-then-jam failure out loud
Design patterns that fit the ATM
You do not need to force patterns in, but naming the ones that fit — and why — is a strong signal in an OOD round.
-
State — the session flow. An
ATMStateinterface with one class per step is the pattern the whole problem is built around; it makes illegal transitions impossible and new steps additive. The standout here. - Chain of Responsibility — cash dispensing across note denominations, where each handler does its part and delegates the remainder.
-
Strategy — the operation types (withdraw, deposit, balance, transfer) can be interchangeable
Transactionobjects, so adding an operation is adding a class. - Singleton — a physical ATM is a single shared instance; note the trade-off that a singleton must be thread-safe and can complicate testing, so keep it injectable.
Say it crisply: State is the backbone because the whole thing is a flow; Chain of Responsibility makes the dispenser clean and extensible; Strategy keeps the operations open for extension; Singleton models the one physical machine but I'd keep it testable. Naming the trade-off, not just the pattern, is what reads as experience.
Low-Level Design
LLD · Core skills
Apply State, Chain of Responsibility and Strategy out loud
How to talk about a low-level design in an interview
The probes from every section rhyme. Master these five and you can defend any LLD, not just the ATM.
- "What are the core classes here?" → Name the nouns — ATM, Card, Account, BankService, CashDispenser, Transaction — and the states the session moves through, and the relationships (has-a, delegates-to) before writing a line. (communication)
-
"How do you stop someone withdrawing before they've authenticated?" → The State pattern:
IdleStateandCardInsertedStatesimply do not offerselectOperation, so the illegal action is impossible by construction, not caught by a scatteredif. (problem solving) - "Dispense 2000 — how, and what if you're out of 500 notes?" → A Chain of Responsibility from the largest denomination down; each handler gives what it can and passes the remainder on, and a leftover at the end means the amount can't be made, so the machine refuses rather than short-changing. (technical execution)
- "You debited the account and then the dispenser jammed — what happens?" → That must never leave a customer out of pocket; I dispense before committing the debit, or issue a compensating credit on a dispense failure, so the balance and the cash can't disagree. (technical execution)
- "What breaks on the unhappy path?" → Wrong PIN with a retry cap, insufficient funds, out of a denomination, a cancelled session, a card left behind — I design the main flow and name these so the model can absorb them. (time management)
Each maps to a debrief dimension. Reading them teaches the answers; it does not teach you to deliver them while someone says "now the dispenser jammed mid-withdrawal" — that only comes from saying them out loud.
How to prepare: a short plan
You do not need to memorise one reference solution. You need the OOD process automatic, so any "design X" prompt becomes requirements → entities → classes → decisions.
-
Day 1: Drill the process on the ATM — clarify, list entities and states, sketch the
ATMStateinterface and the transitions — until you can get a clean state machine on the board in ten minutes. - Day 2: Drill the decisions — State for the session, Chain of Responsibility for the dispenser, and especially transaction atomicity (debit-then-jam) and wrong-PIN handling. These are where the follow-ups live.
- Day 3: Do it out loud, timed, with someone adding a failure mid-answer (the dispenser jams, the machine is out of 500s), then move to a second state-heavy LLD (vending machine) to prove the process transfers.
This explainer feeds the broader low-level design interview track — the ATM is the canonical state-machine OOD problem, and the process it teaches transfers directly to the vending machine, the parking lot and the rest.
Frequently asked questions
What is an ATM object-oriented design interview actually testing?
It is testing object-oriented design, not algorithms. There is no puzzle to crack; instead the interviewer watches how you turn a familiar machine into a clean set of classes, how you model a flow where the legal actions depend entirely on the current step, where you decide each responsibility should live, and whether your design keeps money safe when something fails. The ATM is popular precisely because it is a state machine everyone understands, so the whole conversation is about the quality of your model — your states and transitions, your use of patterns like State and Chain of Responsibility, your handling of failures like a jammed dispenser, and your ability to justify each decision out loud.
What are the main classes in an ATM design?
A standard model has an ATM as the central object that holds the current state, the cash inventory and references to the card reader, screen, keypad and dispenser, and delegates every user action to its current state. The session states — IdleState, CardInsertedState, AuthenticatedState / SelectOperationState and TransactionState — each implement a common ATMState interface. A Card maps to an Account; a BankService stubs authentication and the debit/credit operations; a CashDispenser hands out notes; and Transaction is an abstract operation with withdraw, deposit, balance-inquiry and transfer subclasses. The key is to state the relationships — the ATM has one current state and swaps it, a card maps to an account, the ATM delegates to the bank — not just list the classes.
Why use the State pattern for an ATM?
Because an ATM's valid actions depend entirely on where the session is: entering a PIN only makes sense after a card is inserted, and selecting an operation only makes sense after authentication. The State pattern models each step as its own class implementing a shared ATMState interface, and the ATM simply delegates every action to its current state. This makes illegal actions impossible by construction — IdleState does not even offer selectOperation, so you cannot withdraw before authenticating — instead of relying on a status enum guarded by scattered if checks that are easy to get wrong. It also follows the open/closed principle: a new step is a new state class, and the transition logic lives in one place per state rather than being spread across the machine.
How does an ATM dispense cash, and what if it's out of a denomination?
Cash dispensing is a natural fit for the Chain of Responsibility pattern. You chain handlers for each note denomination from largest to smallest — 500, then 100, then 50 — and each handler dispenses as many of its note as it can for the requested amount, then passes the remaining amount down the chain. Dispensing 2000 becomes "give what 500-notes you can, pass the remainder to 100-notes, then to 50-notes." If any amount is left over when the chain ends — because the machine is out of the right denominations — the machine cannot make the amount and should refuse the withdrawal rather than short-change the customer. This keeps the logic clean and makes adding a new denomination a matter of inserting a handler, not rewriting a nested loop.
How do you keep an ATM transaction safe if the dispenser fails?
This is the classic follow-up: if you debit the account and then the dispenser jams, the customer has paid for cash they never received. The design must make the logical debit and the physical dispense impossible to disagree. One approach is to reorder them — authorise and reserve the funds, dispense the cash, and only commit the debit once the dispense succeeds. Another is to treat it as a transaction with compensation — if the dispense fails after the debit, issue a compensating credit to roll the balance back. Either way, the point to state explicitly is that the two operations can fail independently, so you order them or compensate so a debited customer never walks away empty-handed; designing only the happy path is the mistake the question is hunting for.
How do I practise a low-level design interview out loud?
Reading an ATM solution is not the same as defending your state machine while an interviewer jams the dispenser after the debit. On Zynter, the LLD track runs live voice rounds modelled on how real low-level design loops run — you write your classes and states in the editor and a voice interviewer follows up one question at a time ("how do you stop a withdrawal before authentication?", "you debited the account and the dispenser jammed — now what?", "dispense 2000 with no 500 notes left"), then gives you a debrief on problem solving, communication, technical execution and time management. The score is a practice signal, not a hiring decision.
Practise it on Zynter
You can draw the state machine. The round is won on defending the debit-then-jam failure out loud.
Pick the ATM low-level design round, write your classes and states in the editor, and defend them out loud to a voice interviewer that keeps asking "how do you stop a withdrawal before authentication?" and "you debited the account and then the dispenser jammed — what happens to the customer?" — then read the debrief. Zynter.ai is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the low-level design track (Sep 2026).




Top comments (0)