
State Design Pattern
The State Design Pattern is a behavioral design pattern that lets an object change its behavior when its internal state changes, as if it were switching to a different class at runtime.
It’s particularly useful in situations where:
An object can be in one of many distinct states, each with different behavior.
The object’s behavior depends on current context, and that context changes over time.
You want to avoid large, monolithic
if-else
orswitch
statements that check for every possible state.
When faced with this kind of scenario, developers often start by using conditional logic inside a class to switch behavior based on state variables.
For example, a Document
class might use if-else
blocks to determine what to do based on whether it's in "Draft", "Review", or "Published" state.
But as the number of states grows, this approach becomes hard to scale, difficult to test, and violates the Open/Closed Principle — any new state requires modifying existing logic, increasing the risk of breaking current functionality.
The State Pattern solves this by encapsulating each state into its own class, and letting the context object delegate behavior to the current state object. This makes your code easier to extend, reuse, and maintain — without cluttering the core logic with conditionals.
Let’s walk through a real-world example to see how we can apply the State Pattern to manage dynamic behavior in a clean, scalable, and object-oriented way.
The Problem: Managing Vending Machine States
Imagine you're building a simple vending machine system. On the surface, it seems like a straightforward task: accept money, dispense products, and go back to idle.
But behind the scenes, the machine’s behavior needs to vary depending on its current state.
At any given time, the vending machine can only be in one state, such as:
IdleState – Waiting for user input (nothing selected, no money inserted).
ItemSelectedState – An item has been selected, waiting for payment.
HasMoneyState – Money has been inserted, waiting to dispense the selected item.
DispensingState – The machine is actively dispensing the item.
The machine supports a few user-facing operations:
selectItem(String itemCode)
– Select an item to purchaseinsertCoin(double amount)
– Insert payment for the selected itemdispenseItem()
– Trigger the item dispensing process
Each of these methods should behave differently based on the machine’s current state.
For example:
Calling
dispenseItem()
while the machine is inIdleState
(no item selected, no money inserted) should do nothing or show an error.Calling
insertCoin()
before selecting an item might be disallowed or queued.Calling
selectItem()
duringDispensingState
should be ignored or deferred until the item is dispensed.
The Naive Approach
A common but flawed approach is to manage state transitions manually inside a monolithic VendingMachine
class using if-else
or switch
statements:
What's Wrong with This Approach?
While using an enum
with switch
statements can work for small, predictable systems, this approach doesn't scale well.
1. Cluttered Code
All state-related logic is stuffed into a single class (VendingMachine
), resulting in large and repetitive switch
or if-else
blocks across every method (selectItem()
, insertCoin()
, dispenseItem()
, etc.).
This leads to:
Code that’s hard to read and reason about
Duplicate checks for state across multiple methods
Fragile logic when multiple developers touch the same file
2. Hard to Extend
Suppose you want to introduce new states like:
OutOfStockState
– when the selected item is sold outMaintenanceState
– when the machine is undergoing service
To support these, you'd need to:
Update every switch block in every method
Add logic in multiple places
Risk breaking existing functionality
This violates the Open/Closed Principle — the system is open to modification when it should be open to extension.
3. Violates the Single Responsibility Principle
The VendingMachine
class is now responsible for:
Managing state transitions
Implementing business rules
Executing state-specific logic
This tight coupling makes the class monolithic, hard to test, and resistant to change.
What We Really Need
We need to encapsulate the behavior associated with each state into its own class — so the vending machine can delegate work to the current state object instead of managing it all internally.
This would allow us to:
Avoid switch-case madness
Add or remove states without modifying the core class
Keep each state’s logic isolated and reusable
This is exactly what the State Design Pattern enables.
The State Pattern
The State pattern allows an object (the Context) to alter its behavior when its internal state changes. The object appears to change its class because its behavior is now delegated to a different state object.
Instead of embedding state-specific behavior inside the context class itself, the State Pattern extracts that behavior into separate classes, each representing a distinct state. The context object holds a reference to a state object, and delegates its operations to it.
This results in cleaner, more modular, and extensible code.
Define a State interface (or abstract class) that declares methods representing the actions the Context can perform.
Create ConcreteState classes, each implementing the State interface. Each ConcreteState class implements the behavior specific to that particular state of the Context.
The Context class maintains an instance of a ConcreteState subclass that defines its current state.
When an action is invoked on the Context, it delegates that action to its current State object.
ConcreteState objects are often responsible for transitioning the Context to a new state.
Class Diagram
1. State Interface (e.g., MachineState
)
Declares methods that correspond to the actions the context supports (e.g.,
selectItem()
,insertCoin()
,dispenseItem()
).These methods often take the context as a parameter, so the state can trigger transitions or manipulate context data.
Acts as a common contract for all concrete states.
2. Concrete States (e.g., IdleState
, ItemSelectedState
)
Implement the
State
interface.Define state-specific behavior for each action.
Often responsible for transitioning the context to another state when a specific action occurs.
Can also include state-specific logic (e.g., validation, messaging).
3. Context (e.g., VendingMachine
)
Maintains a reference to the current
State
object.Defines methods for each action (
selectItem()
,insertCoin()
, etc.).Delegates calls to the current state — the state object handles the logic.
Provides a method like
setState()
to allow transitions between states.
Implementing State Pattern
Instead of hardcoding state transitions and behaviors into a single monolithic class using if-else
or switch
statements, we’ll apply the State Pattern to separate concerns and make the vending machine easier to manage and extend.
The first step is to define a MachineState
interface that declares all the operations the vending machine supports.
1. Define the State Interface
This interface represents the contract that all states must follow. It declares methods corresponding to the user-facing operations of the vending machine.
Each state will implement this interface, defining how the vending machine should behave when in that state.
2. Implement Concrete State Classes
Each state class will implement the MachineState
interface and define its behavior for each operation.
🟡 IdleState
🟠ItemSelectedState
🟢 HasMoneyState
🔵 DispensingState
3. Implement the Context (VendingMachine
)
The VendingMachine
class (our context) will maintain a reference to the current state and delegate all actions to it.
Client code
By using the State Pattern, we've transformed a rigid, condition-heavy implementation into a clean, flexible architecture where behaviors and transitions are clearly defined, decoupled, and easy to maintain.
Additional Readings: