Bird
Raised Fist0

State Pattern - Modeling Finite State Machines in ATM, Elevator, Vending Machine

LLDeasyInterview freq: medium
Choose your preparation mode4 modes available
🏗️
State Pattern - Modeling Finite State Machines in ATM, Elevator, Vending Machine
Use State pattern to encapsulate state-specific behavior and transitions in finite state machines like ATM, Elevator, and Vending Machine.
easyDesign Patterns - AppliedFreq: medium
Prerequisites:OOP BasicsFinite State Machines
⚙️
Functional Requirements
  • Encapsulate state-specific behavior and transitions
  • Allow adding new states without modifying existing code (Open-Closed Principle)
  • Support concurrent access with thread-safe state transitions
  • Provide clear state transition methods with validation
  • Allow querying current state
  • Handle invalid transitions gracefully
  • Support timeout or error states
👤
Use Cases
  • User inserts card and enters PIN in ATM
  • User selects floor in Elevator
  • User selects product and inserts money in Vending Machine
  • System transitions between states based on events
  • Maintenance staff resets machine or handles errors
  • Concurrent users interact with the system (e.g., multiple elevator requests)
🧑‍💻
Actors
  • User
  • ATM Machine
  • Elevator Controller
  • Vending Machine Controller
  • Maintenance Staff
❓
Clarifying Questions to Ask
  • How many concurrent threads or users can interact with the machine simultaneously?
  • Are state transitions triggered by external events or internal timers?
  • Is there a pricing strategy that affects state transitions (e.g., discounts in vending machine)?
  • What should happen if an invalid transition is requested?
  • Are there timeout states or error recovery mechanisms?
  • Should the system support rollback or undo of state transitions?
Out of Scope
✗ Detailed UI design
✗ Network communication protocols
✗ Persistence of state across restarts
🔢
Enumerations
enum ATMStateType
IDLE | CARD_INSERTED | PIN_ENTERED | AUTHENTICATED | TRANSACTION_IN_PROGRESS | EJECT_CARD | OUT_OF_SERVICE
Enum prevents typo bugs and enables exhaustive switch statements for ATM states, ensuring all states are handled explicitly.
enum ElevatorStateType
IDLE | MOVING_UP | MOVING_DOWN | DOOR_OPEN | MAINTENANCE
Defines elevator states explicitly, preventing invalid states and enabling clear state transition logic.
enum VendingMachineStateType
WAITING_FOR_SELECTION | WAITING_FOR_PAYMENT | DISPENSING_PRODUCT | OUT_OF_STOCK | MAINTENANCE
Enumerates vending machine states to avoid string-based errors and support exhaustive handling.
📦
State
1 fields
protected Context context
Abstract base class defining common interface and holding reference to Context for state transitions.
Why thisUsing abstract class allows common code reuse and enforces interface for all states.
Why notInterfaces alone lack shared code; enums alone cannot encapsulate behavior.
public abstract void handleEvent(String event)Handle an event that may trigger a state transition.
public abstract Enum<?> getStateType()Return the enum representing the current state type.
📦
Context
1 fields
private volatile State currentState
Maintains current state and delegates event handling to it; manages state transitions atomically.
Why thisContext centralizes state management and ensures thread-safe transitions.
Why notUsing external state management risks concurrency bugs and scattered logic.
public synchronized void setState(State newState)Atomically update the current state to newState.
public State getCurrentState()Return the current state; volatile ensures visibility across threads.
public void handleEvent(String event)Delegate event handling to current state.
📦
ATMContext
1 fields
extends Context
Concrete context for ATM, initializes with IdleState and manages ATM-specific states.
Why thisSpecialized context encapsulates ATM-specific initialization and logic.
Why notUsing generic Context would lose ATM-specific behavior and clarity.
public ATMContext()Initialize ATM context with IdleState.
📦
ElevatorContext
1 fields
extends Context
Concrete context for Elevator, manages elevator states and transitions.
Why thisElevator-specific context handles elevator domain logic cleanly.
Why notGeneric context would mix concerns and reduce maintainability.
public ElevatorContext()Initialize Elevator context with IdleState.
📦
VendingMachineContext
1 fields
extends Context
Concrete context for Vending Machine, manages vending machine states and transitions.
Why thisEncapsulates vending machine domain logic and state initialization.
Why notGeneric context would not capture vending machine specifics.
public VendingMachineContext()Initialize Vending Machine context with WaitingForSelectionState.
📦
IdleState
1 fields
extends State
Represents the idle state; handles events to transition to next states.
Why thisConcrete state encapsulates behavior for idle condition.
Why notUsing flags or conditionals would scatter logic and reduce extensibility.
public void handleEvent(String event)Handle events specific to Idle state.
public Enum<?> getStateType()Return the enum representing Idle state.
📦
CardInsertedState
1 fields
extends State
Represents the state after card insertion; handles PIN entry and validation.
Why thisEncapsulates card inserted logic separately for clarity and maintainability.
Why notMixing states would complicate code and violate single responsibility.
public void handleEvent(String event)Handle PIN entry and transition events.
public Enum<?> getStateType()Return the enum representing CardInserted state.
📦
TransactionInProgressState
1 fields
extends State
Represents transaction processing state; handles transaction events and completion.
Why thisSeparates transaction logic for modularity and easier testing.
Why notCombining with other states would reduce clarity and increase bugs.
public void handleEvent(String event)Handle transaction events and transition to completion or error.
public Enum<?> getStateType()Return the enum representing TransactionInProgress state.
🔗
Relationships
Context◆──►State[1 to 1]
compositionContext owns State lifecycle; State cannot exist without Context
State──►Context[1 to 1]
associationState holds reference to Context but does not own it
ATMContext──►Context[1 to 1]
inheritanceATMContext is a specialized Context subclass
ElevatorContext──►Context[1 to 1]
inheritanceElevatorContext is a specialized Context subclass
VendingMachineContext──►Context[1 to 1]
inheritanceVendingMachineContext is a specialized Context subclass
🗄️
StateTransitions
PK: machine_id
machine_id STRINGtimestamp TIMESTAMPprevious_state STRINGnew_state STRINGevent STRING
Stores state transition history for each machine.
🗄️
MachineStatus
PK: machine_id
machine_id STRINGcurrent_state STRINGlast_updated TIMESTAMPerror_code STRING
Stores current status and state of each machine.
🔌
POST /machines/{id}/event
Request
{
  "event": "string"
}
Response
{
  "status": "string",
  "current_state": "string"
}
Accepts an event to trigger state transition for a machine.
🔌
GET /machines/{id}/state
Request
{}
Response
{
  "current_state": "string",
  "last_updated": "timestamp"
}
Returns current state of the machine.
🔌
POST /machines/{id}/reset
Request
{}
Response
{
  "status": "string"
}
Resets machine to initial state, used by maintenance staff.

Practice

(1/5)
1. The ATM system currently calculates transaction fees using a FeeCalculator class. To add a WeekendFeeStrategy without modifying existing classes, which SOLID principle applies and what is the minimal correct change?
medium
A. Add a boolean flag isWeekend in FeeCalculator and branch logic accordingly, maintaining SRP.
B. Modify FeeCalculator to add weekend logic, violating OCP but keeping code simple.
C. Create WeekendFeeStrategy implementing FeeStrategy interface; FeeCalculator uses FeeStrategy via dependency injection, adhering to OCP and DIP.
D. Create WeekendFeeStrategy subclassing FeeCalculator and override fee calculation, violating DIP.

Solution

  1. Step 1: Identify applicable SOLID principles

    Open-Closed Principle (OCP) requires FeeCalculator not be modified for new fee types. Dependency Inversion Principle (DIP) requires FeeCalculator depend on abstractions.
  2. Step 2: Apply correct design

    Create WeekendFeeStrategy implementing FeeStrategy interface. Inject FeeStrategy into FeeCalculator. This adds new behavior without modifying existing code.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    New strategy class + DI -> OCP and DIP respected [check]
Hint: New fee? Add strategy class, inject via interface [check]
Common Mistakes:
2. In the in-memory file system, the createFile() method checks if a filename is available and then adds the file to the directory without synchronization. Which exact thread interleaving causes two threads to create files with the same name (double creation)?
medium
A. Thread 1 checks filename availability -> true, context switch; Thread 2 checks availability -> true; both add file with same name.
B. Thread 1 and Thread 2 both call createFile() simultaneously and both fail due to race condition.
C. Thread 1 adds file, then Thread 2 checks availability and finds it unavailable, so no conflict occurs.
D. Thread 1 deletes a file while Thread 2 creates a file with the same name, causing inconsistency.

Solution

  1. Step 1: Trace the check-then-act sequence without synchronization

    Thread 1 calls isNameAvailable() -> true, but before adding the file, context switches to Thread 2.
  2. Step 2: Thread 2 also calls isNameAvailable() -> true and proceeds to add the file, then Thread 1 resumes and adds its file too.

    This causes double creation due to the race condition on the availability check and add operation.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Check-then-act gap without sync -> double creation [check]
Hint: Check availability -> context switch -> both add same file [check]
Common Mistakes:
3. You want to add a WeekendFeeStrategy to the parking lot pricing system. Which minimum change respects SOLID principles?
medium
A. Add a boolean flag isWeekend to Vehicle and adjust pricing accordingly.
B. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without changing existing classes.
C. Modify the existing FeeCalculator class to add weekend logic inside.
D. Create a subclass WeekendFeeCalculator extending FeeCalculator and override methods.

Solution

  1. Step 1: Identify SOLID principle involved

    Open-Closed Principle (OCP) requires adding new functionality without modifying existing code.
  2. Step 2: Evaluate options

    Modify the existing FeeCalculator class to add weekend logic inside. violates OCP by modifying existing class. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without changing existing classes. respects OCP by adding new strategy class. Add a boolean flag isWeekend to Vehicle and adjust pricing accordingly. incorrectly mixes state into Vehicle, violating SRP. Create a subclass WeekendFeeCalculator extending FeeCalculator and override methods. adds subclass but may cause class explosion and tight coupling.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    New strategy class + no existing class change -> OCP respected [check]
Hint: Add new strategy class, avoid modifying existing code [check]
Common Mistakes:
4. You want to add a WeekendFeeStrategy to calculate weekend parking fees without modifying existing classes. Which SOLID principle guides the minimum change, and what is the correct approach?
medium
A. Modify the ParkingLot class to add weekend fee logic, enforcing Single Responsibility Principle.
B. Add weekend fee logic inside existing FeeStrategy implementations, following Dependency Inversion Principle.
C. Create a WeekendFeeStrategy subclass of ParkingLot to override fee calculation, following Liskov Substitution Principle.
D. Create a new WeekendFeeStrategy class implementing FeeStrategy interface, and inject it where needed, following Open/Closed Principle.

Solution

  1. Step 1: Identify the SOLID principle for extensibility

    Open/Closed Principle (OCP) requires adding new functionality without modifying existing code.
  2. Step 2: Evaluate approaches

    Creating a new FeeStrategy implementation and injecting it respects OCP and DIP; modifying ParkingLot or existing strategies violates OCP or mixes responsibilities.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    New FeeStrategy class + injection -> OCP and DIP respected [check]
Hint: New strategy class + injection -> Open/Closed Principle [check]
Common Mistakes:
5. You need to add support for ElectricCar and ChargingSpot in the parking system without modifying existing classes. What is the minimum change to the current design?
hard
A. Modify Vehicle and Spot classes to include electric-specific fields and logic.
B. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update compatibility map externally.
C. Create a new ElectricVehicle interface and make ElectricCar implement it, modifying existing classes to check for this interface.
D. Add a boolean isElectric flag to Vehicle and Spot classes and handle logic with conditionals.

Solution

  1. Step 1: Identify extensibility without modifying existing classes

    Enums can be extended with new constants, and new subclasses can be added without changing existing code.
  2. Step 2: Use external compatibility map update

    Updating the compatibility map externally avoids modifying existing classes, preserving OCP and avoiding class explosion.
  3. Step 3: Avoid flags or interface checks that require existing class changes

    Flags or interface checks require modifying existing classes, violating OCP.
  4. Final Answer:

    Option B -> Option B
  5. Quick Check:

    Extend enums + new subclass + external map update -> minimal change [check]
Hint: Extend enums + subclass + external map -> minimal change [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