AlgoMaster Logo

State Design Pattern

Last Updated: February 24, 2026

Ashish

Ashish Pratap Singh

7 min read

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.

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.

1. The Problem: Managing Vending Machine States

Imagine you're building a simple vending machine system. On the surface, it seems straightforward: accept money, dispense products, and go back to idle.

Loading simulation...

But the tricky part is that the machine’s behavior must change depending on what’s happening right now. A vending machine can be in only one state at a time, for example:

  • 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 should do nothing or show an error. Calling insertCoin() before selecting an item should be disallowed. Calling selectItem() during DispensingState should be ignored 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. This leads to code that is hard to read and reason about, duplicate checks for state across multiple methods, and 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) or MaintenanceState (when the machine is undergoing service). To support these, you would need to update every switch block in every method, add logic in multiple places, and risk breaking existing functionality. This violates the Open/Closed Principle.

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, and 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, and keep each state's logic isolated and testable.

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.

Two characteristics define the pattern:

  1. Encapsulation of state-specific behavior: Each state gets its own class. All the logic for "what happens when the machine is idle and someone inserts a coin" lives in the IdleState class, not buried in a switch statement somewhere.
  2. State-driven transitions: State objects themselves decide when and how to transition to another state. The context does not manage transitions through conditionals. It just delegates to the current state, and the state handles the rest.

Class Diagram

1. State Interface (e.g., MachineState)

Declares the methods that correspond to the actions the context supports. Every concrete state must implement these methods, even if some are no-ops in certain states.

The State interface usually passes the context as a parameter to each method. This lets concrete states call context.setState(new SomeOtherState()) to trigger transitions.

2. Concrete States (e.g., IdleStateItemSelectedState)

Each concrete state implements the State interface with behavior specific to that state.

When an action in one state should move the context to a different state, the concrete state creates the next state object and sets it on the context.

3. Context (e.g., VendingMachine)

The class that clients interact with. It maintains a reference to the current State object and delegates all operations to it.

3. How it Works

The State workflow follows a delegation-and-transition cycle:

Step 1: The context starts with an initial state (e.g., IdleState).

Step 2: The client calls an action on the context (e.g., selectItem("A1")).

Step 3: The context delegates the call to the current state: currentState.selectItem(this, "A1").

Step 4: The state performs its logic. If the action triggers a transition, the state creates a new state object and calls context.setState(newState).

Step 5: The next time the client calls an action, the context delegates to the new state, which may behave completely differently.

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

State Diagram

Step 1: Define the State Interface

The first step is to define a MachineState interface that declares all the operations the vending machine supports. Each state will implement this interface, defining how the vending machine should behave when in that state.

Notice how every method takes the context as a parameter. This allows each state to read context data (like the selected item) and trigger transitions by calling context.setState(...).

Step 2: Implement Concrete State Classes

Each state class implements the MachineState interface and defines its behavior for each operation.

IdleState

The machine is waiting for user input. The only valid action is selecting an item. Inserting coins or dispensing without selecting first should be rejected.

ItemSelectedState

An item has been selected, and the machine is waiting for payment. The only valid action here is inserting a coin.

HasMoneyState

Money has been inserted. The machine is ready to dispense. Selecting a new item or inserting more money should be rejected.

DispensingState

The machine is actively dispensing. All actions should be rejected until dispensing completes.

Step 3: Implement the Context (VendingMachine)

The VendingMachine class (our context) maintains a reference to the current state and delegates all actions to it. It also holds the shared data that states need access to.

Client code

By using the State pattern, we have transformed a rigid, condition-heavy implementation into a clean, flexible architecture where behaviors and transitions are clearly defined, decoupled, and easy to maintain. Adding a new state like OutOfStockState means creating one new class that implements MachineState. No existing state classes or the context need to change.

5. Practical Example: Document Workflow

Let us work through a second example to reinforce the pattern. This time, we are building a document management system where documents move through a workflow: Draft, Under Review, and Published. Each state has different rules for what operations are allowed.

In Draft state, authors can edit the document and submit it for review. In Review state, reviewers can approve or reject it. In Published state, the document is read-only and can only be unpublished to go back to Draft.

Implementation

This example reinforces the same principles as the vending machine but in a different domain. Notice how each state cleanly defines what is allowed and what is not, and how transitions are handled by the states themselves. Adding a new state like ArchivedState would mean creating one new class without touching any existing code.