Bird
Raised Fist0

Design an ATM System - States, Transitions, Card, PIN, Transaction, Security

LLDmediumInterview freq: high
Choose your preparation mode4 modes available
๐ŸŽฏ
Design an ATM System with States, Transitions, Card, PIN, Transaction, Security
40 min ยท medium
medium

Design an ATM system that manages card insertion, PIN verification, transaction selection, and completion with proper state transitions and security checks.

  • ATM supports multiple states: Idle, CardInserted, PinVerified, TransactionSelected, TransactionProcessing, and EjectCard.
  • Card has a card number and encrypted PIN; PIN verification must be secure and thread-safe.
  • Transactions include Withdrawal, Deposit, and Balance Inquiry with distinct behaviors.
  • Security requires blocking card after 3 failed PIN attempts and secure PIN storage.
  • Multiple concurrent users can access different ATMs; each ATM instance manages its own state.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define ATM States and Transitions
Design an enum to represent ATM states and a mechanism to transition between states safely.
๐Ÿ’ก Hint
Use Java enum for states to ensure type safety and exhaustive switch statements.
Use enum ATMState {IDLE, CARD_INSERTED, PIN_VERIFIED, TRANSACTION_SELECTED, TRANSACTION_PROCESSING, EJECT_CARD} - enum ensures compile-time safety and exhaustive switch checks in Java.
Mid: Names enum and describes state transitions. Senior: Explains how enum prevents invalid states and how to use switch with default to catch unhandled states.
โš ๏ธCommon Wrong Answer
Using String constants for states leads to runtime bugs due to typos and no compile-time checks.
Step 2
Design Card and PIN Verification with Security
Design Card class with encrypted PIN storage and implement thread-safe PIN verification with retry limit.
๐Ÿ’ก Hint
Use private final fields for card data and synchronized method for PIN verification to prevent race conditions on retry count.
Card class with private final String cardNumber and volatile int failedPinAttempts; synchronized boolean verifyPin(String inputPin) to atomically check PIN and increment failedPinAttempts.
Mid: Describes synchronized method for atomic PIN check and retry increment. Senior: Traces thread interleaving where two threads verify PIN simultaneously causing inconsistent failedPinAttempts without synchronization.
โš ๏ธCommon Wrong Answer
Not synchronizing PIN verification allows race conditions where failedPinAttempts is incorrectly updated, potentially bypassing card blocking.
Step 3
Model Transactions Using Abstract Class and Polymorphism
Design an abstract Transaction class and concrete subclasses for Withdrawal, Deposit, and Balance Inquiry.
๐Ÿ’ก Hint
Use abstract class because Transaction has shared instance state like amount and timestamp; interfaces cannot hold state in Java.
abstract class Transaction with protected fields amount, timestamp; abstract method execute(Account account); concrete subclasses override execute with specific logic.
Mid: Identifies abstract class for shared state and polymorphism. Senior: Explains why interface is insufficient due to instance fields and how abstract class enforces common behavior.
โš ๏ธCommon Wrong Answer
Using interface for Transaction leads to duplicated state management and violates DRY principle.
Step 4
Implement ATM Class with State Management and Synchronization
Design ATM class that holds current state and processes card insertion, PIN entry, and transactions with thread-safe state transitions.
๐Ÿ’ก Hint
Use private volatile ATMState currentState; synchronized methods for state-changing operations to prevent race conditions.
ATM class with private volatile ATMState currentState; public synchronized boolean insertCard(Card card) to atomically check currentState and set CARD_INSERTED; similar synchronized methods for PIN entry and transaction processing.
Mid: Uses synchronized methods to protect state transitions. Senior: Details thread interleaving where two threads insert cards simultaneously causing inconsistent states without synchronization.
โš ๏ธCommon Wrong Answer
Not using volatile and synchronized leads to visibility issues and race conditions on currentState, causing invalid ATM behavior.
Step 5
Design Security Mechanism to Block Card After Failed Attempts
Implement card blocking logic after 3 failed PIN attempts and ensure thread-safe state update.
๐Ÿ’ก Hint
Use volatile boolean isBlocked in Card; synchronized method to update and check block status atomically.
volatile boolean isBlocked in Card; synchronized boolean verifyPin(String inputPin) updates failedPinAttempts and sets isBlocked=true after 3 failures atomically.
Mid: Describes blocking logic and synchronized update. Senior: Explains how volatile ensures visibility of isBlocked across threads and synchronized prevents race conditions on failedPinAttempts.
โš ๏ธCommon Wrong Answer
Without volatile and synchronization, one thread may not see updated block status, allowing blocked cards to be used.
๐Ÿ”Œ
API Design Challenge
Design the API endpoints and method signatures for the ATM system to handle card insertion, PIN verification, transaction processing, and card ejection with concurrency safety.
  • Use synchronized methods or locks to ensure thread-safe state transitions in ATM class.
  • Design clear method signatures for insertCard(Card card), verifyPin(String pin), selectTransaction(Transaction txn), and ejectCard().
  • Consider returning status codes or exceptions to indicate success, failure, or blocked card states.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • Use enum for ATM states with exhaustive switch
  • Card class with final fields and volatile failedPinAttempts
  • synchronized PIN verification method to prevent race conditions
  • abstract Transaction class with shared state and polymorphism
  • ATM class with volatile state and synchronized state transitions
Good to Have - 30%
  • volatile boolean isBlocked in Card for visibility
  • Concrete Transaction subclasses overriding execute
  • Lifecycle ownership clearly defined (ATM aggregates Card)
  • Explanation of thread interleaving causing race conditions
  • Use of Java-specific concurrency keywords (volatile, synchronized)
Bonus - 10%
  • Detailed concurrency scenario tracing
  • Use of defensive copying or immutability for Card data
  • Extensibility for new transaction types without modifying existing classes
  • Clear separation of concerns following SOLID principles
Using String constants for states leads to runtime bugs due to typos and no compile-time checks, causing invalid state transitions.
Not synchronizing PIN verification causes race conditions and inconsistent failed attempt counts, potentially allowing unauthorized access.
Using interface instead of abstract class for Transaction duplicates state and violates DRY, leading to code duplication and maintenance issues.
Not using volatile for shared mutable state causes visibility issues across threads, resulting in stale or inconsistent data.
Allowing Card to exist independently of ATM session breaks lifecycle ownership, leading to potential security and state management issues.
โœ…
Model Answer
โ–ผ Reveal

    • Use Java enum for ATMState to ensure compile-time safety and exhaustive switch statements.
    • Card class stores cardNumber as final String and uses volatile int failedPinAttempts for concurrency visibility.
    • synchronized boolean verifyPin(String inputPin) in Card to atomically check PIN and update failed attempts.
    • abstract class Transaction holds shared state (amount, timestamp) and defines abstract execute(Account) method.
    • Concrete Transaction subclasses override execute with specific logic for Withdrawal, Deposit, and Balance Inquiry.
    • ATM class holds volatile ATMState currentState and uses synchronized methods for atomic state transitions.
    • volatile boolean isBlocked in Card ensures visibility of block status across threads.
    • Use synchronized methods in Card and ATM to prevent race conditions on mutable state.
    • Lifecycle: ATM aggregates Card (Card cannot exist independently of ATM session).
    • Follow Single Responsibility Principle by separating Card, Transaction, and ATM state management.

    Practice

    (1/5)
    1. A RateLimiter class has a private constructor and a static synchronized getInstance() method returning a single instance. Which design pattern is applied and why is this the correct choice for a token bucket rate limiter?
    easy
    A. Builder pattern to configure token bucket parameters fluently
    B. Factory pattern to create multiple rate limiter instances with different limits
    C. Singleton pattern to ensure a single shared token bucket across threads
    D. Prototype pattern to clone rate limiter instances for each client

    Solution

    1. Step 1: Identify the pattern from constructor and getInstance()

      The private constructor and static synchronized getInstance() method indicate Singleton pattern, ensuring only one instance exists.
    2. Step 2: Why Singleton fits token bucket rate limiter

      Singleton ensures a single shared token bucket state across threads, preventing inconsistent token counts.
    3. Final Answer:

      Option C -> Option C
    4. Quick Check:

      Singleton enforces single shared instance for thread-safe token bucket [check]
    Hint: Private constructor + static synchronized getInstance() -> Singleton [check]
    Common Mistakes:
    2. In designing a TokenBucketRateLimiter class, should the RateLimiter be an abstract class or an interface? Consider that the rate limiter maintains internal token state and exposes tryConsume() behavior.
    easy
    A. Abstract class, because it can encapsulate shared token state and partial implementation
    B. Interface, because it only defines behavior and state is managed externally
    C. Interface, because Java interfaces can have private fields for token state
    D. Abstract class, because interfaces cannot define any methods

    Solution

    1. Step 1: Analyze state and behavior requirements

      RateLimiter needs to maintain internal token count state and provide tryConsume() behavior.
    2. Step 2: Abstract class vs interface for stateful behavior

      Abstract class can hold shared mutable state and provide partial implementation; interfaces cannot hold instance state.
    3. Final Answer:

      Option A -> Option A
    4. Quick Check:

      Abstract class supports shared token state and behavior [check]
    Hint: Stateful base class -> abstract class, not interface [check]
    Common Mistakes:
    3. You want to add a new WeekendFeeStrategy to the existing chain of responsibility-based parking fee system. Which change respects the Open-Closed Principle (OCP) and Dependency Inversion Principle (DIP)?
    medium
    A. Modify the existing FeeHandler class to add weekend fee logic inside its handleRequest() method.
    B. Create a subclass WeekendFeeHandler extending FeeHandler and override handleRequest() with weekend logic.
    C. Create a new WeekendFeeHandler implementing the FeeStrategy interface and add it to the chain without changing existing handlers.
    D. Add a boolean flag isWeekend to FeeHandler and branch logic based on it.

    Solution

    1. Step 1: Understand OCP and DIP

      OCP requires extending behavior without modifying existing code; DIP requires depending on abstractions, not concretions.
    2. Step 2: Evaluate options

      Modify the existing FeeHandler class to add weekend fee logic inside its handleRequest() method. modifies existing class, violating OCP; Create a new WeekendFeeHandler implementing the FeeStrategy interface and add it to the chain without changing existing handlers. adds new handler, respecting OCP and DIP; Add a boolean flag isWeekend to FeeHandler and branch logic based on it. adds flag inside existing handler, violating OCP; Create a subclass WeekendFeeHandler extending FeeHandler and override handleRequest() with weekend logic. subclasses but may cause tight coupling and violates DIP if chain depends on concrete classes.
    3. Final Answer:

      Option D -> Option D
    4. Quick Check:

      New handler class added without modifying existing code [check]
    Hint: Add new handler class, don't modify existing ones [check]
    Common Mistakes:
    4. You want to add a WeekendFeeStrategy to calculate message delivery fees differently on weekends. According to SOLID principles, what is the minimum change required in the current design?
    medium
    A. Add a boolean flag isWeekend in Message and handle fees there.
    B. Create a new WeekendFeeStrategy class implementing the existing FeeStrategy interface and inject it where needed.
    C. Modify the existing FeeCalculator class to add weekend logic with an if-else block.
    D. Create a subclass WeekendFeeCalculator extending FeeCalculator and override fee calculation.

    Solution

    1. Step 1: Identify the SOLID principle involved

      Open-Closed Principle (OCP) requires adding new behavior without modifying existing classes.
    2. Step 2: Evaluate options against OCP

      Add a boolean flag isWeekend in Message and handle fees there. adds weekend logic inside Message, violating SRP and mixing concerns; Modify the existing FeeCalculator class to add weekend logic with an if-else block. modifies existing class, violating OCP; Create a subclass WeekendFeeCalculator extending FeeCalculator and override fee calculation. creates subclass but may cause class explosion; Create a new WeekendFeeStrategy class implementing the existing FeeStrategy interface and inject it where needed. adds new strategy class implementing FeeStrategy, adhering to OCP and DIP.
    3. Final Answer:

      Option A -> Option A
    4. Quick Check:

      New strategy class -> OCP and DIP respected [check]
    Hint: New strategy class -> Open-Closed Principle [check]
    Common Mistakes:
    5. Adding a WeekendFeeStrategy to calculate weekend parking fees requires which minimum change to the current design that uses a FeeStrategy interface?
    medium
    A. Combine all fee calculations into one class with if-else for weekend and weekday fees
    B. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without modifying existing classes
    C. Modify the existing FeeStrategy interface to add a isWeekend() method and update all implementations
    D. Add a boolean flag isWeekend in ParkingLot and conditionally apply fees inside existing strategies

    Solution

    1. Step 1: Understand current design with FeeStrategy interface

      FeeStrategy interface allows different fee calculation implementations, supporting OCP if new strategies are added as new classes.
    2. Step 2: Analyze minimal change for weekend fees

      Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without modifying existing classes respects OCP by adding a new class without modifying existing ones. Add a boolean flag isWeekend in ParkingLot and conditionally apply fees inside existing strategies modifies ParkingLot to add weekend logic, violating OCP but minimal change.
    3. Final Answer:

      Option D -> Option D
    4. Quick Check:

      Minimal change but violates OCP -> modify ParkingLot with weekend flag [check]
    Hint: Minimal change โ‰  OCP compliance; adding flag is minimal but not ideal [check]
    Common Mistakes:
    Recommended

    Ready to prove it?

    Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

    10 questions~5 mineasy hard