AlgoMaster Logo

State Design Pattern

Ashish

Ashish Pratap Singh

7 min read

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 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 scaledifficult 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.

Vending Machine

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 purchase
  • insertCoin(double amount) – Insert payment for the selected item
  • dispenseItem() – 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 in IdleState (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() during DispensingState 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 out
  • MaintenanceState – 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.

2. 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.

  1. Define a State interface (or abstract class) that declares methods representing the actions the Context can perform.
  2. Create ConcreteState classes, each implementing the State interface. Each ConcreteState class implements the behavior specific to that particular state of the Context.
  3. The Context class maintains an instance of a ConcreteState subclass that defines its current state.
  4. When an action is invoked on the Context, it delegates that action to its current State object.
  5. 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., IdleStateItemSelectedState)

  • 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.

3. 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.