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:
if-else
or switch
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.
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:
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 processEach of these methods should behave differently based on the machine’s current state.
For example:
dispenseItem()
while the machine is in IdleState
(no item selected, no money inserted) should do nothing or show an error.insertCoin()
before selecting an item might be disallowed or queued.selectItem()
during DispensingState
should be ignored or deferred until the item is dispensed.A common but flawed approach is to manage state transitions manually inside a monolithic VendingMachine
class using if-else
or switch
statements:
While using an enum
with switch
statements can work for small, predictable systems, this approach doesn't scale well.
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:
Suppose you want to introduce new states like:
OutOfStockState
– when the selected item is sold outMaintenanceState
– when the machine is undergoing serviceTo support these, you'd need to:
This violates the Open/Closed Principle — the system is open to modification when it should be open to extension.
The VendingMachine
class is now responsible for:
This tight coupling makes the class monolithic, hard to test, and resistant to change.
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:
This is exactly what the State Design Pattern enables.
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.
MachineState
)selectItem()
, insertCoin()
, dispenseItem()
).IdleState
, ItemSelectedState
)State
interface.VendingMachine
)State
object.selectItem()
, insertCoin()
, etc.).setState()
to allow transitions between states.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.
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.
Each state class will implement the MachineState
interface and define its behavior for each operation.
VendingMachine
)The VendingMachine
class (our context) will maintain a reference to the current state and delegate all actions to it.
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.