Chaturmind
LearnDSASystem DesignDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
Chaturmind
← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
HomeLearnSystem DesignLow-Level Design & Design PatternsMachine Coding Exercises
✓ FreeIntermediate· 7 min read

ATM System Design

Modeling withdrawal/deposit/balance-inquiry as Command objects, and the concurrency question that actually matters: two ATMs hitting the same account at once.

Published September 23, 2026


ATM System Design

Core classes

class Account {
    private final String accountId;
    private double balance;
    private long version; // for optimistic locking — see below
}

class Card {
    private final String cardNumber;
    private final String accountId;
    private final String pinHash;
}

class CashDispenser {
    private final Map<Integer, Integer> denominationCounts; // e.g. {2000: 10, 500: 40, 100: 100}
    boolean canDispense(int amount) { /* greedy or DP check against available denominations */ return true; }
    void dispense(int amount) { /* decrement denomination counts */ }
}

class ATM {
    private final CashDispenser dispenser;
    private final AccountRepository accountRepository;
}

Transaction types via Command (or Strategy)

interface ATMTransaction { TransactionResult execute(Account account); }

class WithdrawalTransaction implements ATMTransaction {
    private final double amount;
    private final CashDispenser dispenser;

    public TransactionResult execute(Account account) {
        if (account.getBalance() < amount) return TransactionResult.failure("Insufficient funds");
        if (!dispenser.canDispense((int) amount)) return TransactionResult.failure("Dispenser cannot fulfill this amount");
        account.debit(amount);
        dispenser.dispense((int) amount);
        return TransactionResult.success();
    }
}

class DepositTransaction implements ATMTransaction {
    private final double amount;
    public TransactionResult execute(Account account) {
        account.credit(amount);
        return TransactionResult.success();
    }
}

class BalanceInquiryTransaction implements ATMTransaction {
    public TransactionResult execute(Account account) { return TransactionResult.withBalance(account.getBalance()); }
}

This is Command Pattern (see Command Pattern), not just Strategy: each transaction type is a request object that can be constructed, validated, and executed as a discrete unit — and critically, this shape is what naturally supports building a transaction history/audit log (every executed ATMTransaction is itself a natural log entry) and potential reversal logic, which a pure Strategy ("pick an algorithm") framing doesn't emphasize as directly.

Concurrency: two ATMs, one account, at the same time

This is the question every interviewer eventually asks for this prompt, and it's the one worth spending real design time on. The consistency boundary is the account's balance check-and-debit, and it needs to be atomic across any ATM touching that account, not just within one ATM process.

class AccountRepository {
    // Optimistic locking — see Locking Strategies for the general pattern
    boolean debitWithVersionCheck(String accountId, double amount, long expectedVersion) {
        // UPDATE accounts SET balance = balance - ?, version = version + 1
        // WHERE account_id = ? AND version = ? AND balance >= ?
        // returns false (0 rows affected) if the version changed since it was read, OR balance is insufficient
        return database.executeUpdate(
            "UPDATE accounts SET balance = balance - ?, version = version + 1 WHERE account_id = ? AND version = ? AND balance >= ?",
            amount, accountId, expectedVersion, amount
        ) > 0;
    }
}

class WithdrawalTransaction implements ATMTransaction {
    public TransactionResult execute(Account account) {
        boolean success = accountRepository.debitWithVersionCheck(account.getId(), amount, account.getVersion());
        if (!success) return TransactionResult.failure("Concurrent modification — please retry"); // the OTHER ATM won the race
        dispenser.dispense((int) amount);
        return TransactionResult.success();
    }
}

This is the same optimistic-locking pattern from Locking Strategies, applied to the exact scenario it's built for: two ATMs both reading the same account balance, both attempting to debit — the database-level conditional UPDATE (checking version and balance >= amount atomically, in the database, not in application code) guarantees only one of the two concurrent withdrawal attempts succeeds if the combined amount would overdraw the account, regardless of which ATM's application code runs first. Application-level locking (a Java synchronized block, a ReentrantLock) cannot solve this, because the two ATMs are almost certainly separate processes (potentially on separate machines) — the consistency boundary has to live in the shared database, not in any single application's memory.

Follow-up questions this topic invites — and their answers

Q: Why optimistic locking here rather than pessimistic (SELECT ... FOR UPDATE)? A: ATM withdrawals from the same account happening genuinely simultaneously are rare (most accounts aren't being hit by two ATMs at once) — optimistic locking fits low-contention scenarios well (see Locking Strategies), avoiding the cost of holding a database lock across the round-trip to the cash dispenser hardware, which pessimistic locking would require for correctness.

Q: What should the ATM do when debitWithVersionCheck() fails due to a lost race? A: Re-read the current account state and either retry the whole transaction (if the now-current balance still supports it) or surface a clear failure to the user — silently retrying without re-checking balance could still overdraw if the losing ATM's original amount is no longer affordable.

Q: Why check canDispense() on the CashDispenser separately from the balance check? A: Two genuinely independent failure modes: insufficient account balance is a business-logic failure, while a dispenser physically unable to make exact change for a requested amount (e.g. requesting $30 when the machine only stocks $20 and $50 notes) is a hardware/inventory constraint — conflating them would produce a confusing error message that doesn't tell the user which problem actually occurred.

Q: How would you extend TransactionResult logging into a full audit trail? A: Each executed ATMTransaction (with its type, amount, timestamp, resulting account version) becomes a row in a transactions table — since Command objects already encapsulate everything needed to describe 'what happened,' persisting the command itself (or its result) after execution is a natural, low-effort audit log, not a bolted-on afterthought.

Previous

Vending Machine Design

Next

In-Memory Rate Limiter

AI Tutor

Lesson: ATM System Design

Quick actions

AI responses can be inaccurate. Verify critical information.