Skip to content
Back to skills

Apply State Pattern

ASecurity

Use when an object's behavior changes based on its internal state and the logic for each state is complex — replacing large conditional chains with state objects where each state encapsulates its own behavior.

  • 4 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 8, 2026
ai-agentsjavascriptpythongojavatestingdatabasedocumentation

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add jeffreytse/grimoire-core --skill apply-state-pattern --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Apply State Pattern?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Apply State Pattern
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jeffreytse-apply-state-pattern/badge)](https://www.skillsdirectory.com/skills/jeffreytse-apply-state-pattern)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: apply-state-pattern
description: Use when an object's behavior changes based on its internal state and the logic for each state is complex — replacing large conditional chains with state objects where each state encapsulates its own behavior.
source: "Gamma, Helm, Johnson, Vlissides, \"Design Patterns: Elements of Reusable Object-Oriented Software\" (1994) pp. 305–313; TCP connection state machine; finite state machines in game AI; XState (JavaScript statechart library); vending machine controllers"
tags: [design-patterns, behavioral, state, oop, developer, state-machine, conditional-elimination]
related: [apply-strategy-pattern, apply-command-pattern, apply-solid-principles]
---

# Apply State Pattern

Allow an object to alter its behavior when its internal state changes — by delegating behavior to state objects rather than using conditional logic.

## Why This Is Best Practice

**Adopted by:** XState (JavaScript statechart library — 1.5M weekly npm downloads)
implements State pattern as its core model. TCP protocol stack implements state as
objects (CLOSED, LISTEN, SYN_SENT, ESTABLISHED, etc. — each with its own behavior for
each operation). Unity's Animator Controller is a visual state machine based on the
same pattern. Every parser generator (ANTLR, PLY) produces state-based recognizers.
**Impact:** GoF documents that without the State pattern, a class with 4 states and
6 operations has 4×6=24 conditional branches scattered across all methods. The State
pattern consolidates each state's behavior into one class — 4 state classes of 6 methods
each. Adding a 5th state requires one new class, not editing 6 existing methods.
**Why best:** `if/elif` state dispatch — the alternative — scatters state logic across
every method, making it hard to add states or understand what happens in a given state.
State objects make each state's behavior cohesive, isolated, and independently testable.

Sources: Gamma et al. (1994) pp. 305–313; XState documentation; RFC 793 (TCP state machine)

## Steps

### Step 1: Define the state interface with all state-dependent methods

```python
from abc import ABC, abstractmethod

class OrderState(ABC):
    @abstractmethod
    def pay(self, order: "Order") -> None: ...

    @abstractmethod
    def ship(self, order: "Order") -> None: ...

    @abstractmethod
    def cancel(self, order: "Order") -> None: ...
```

### Step 2: Implement concrete state classes — each handles its own transitions

```python
class PendingState(OrderState):
    def pay(self, order: "Order") -> None:
        print("Payment accepted")
        order.state = PaidState()

    def ship(self, order: "Order") -> None:
        print("Cannot ship — order not paid")

    def cancel(self, order: "Order") -> None:
        print("Order cancelled")
        order.state = CancelledState()

class PaidState(OrderState):
    def pay(self, order: "Order") -> None:
        print("Already paid")

    def ship(self, order: "Order") -> None:
        print("Order shipped")
        order.state = ShippedState()

    def cancel(self, order: "Order") -> None:
        print("Cancelling paid order — refund issued")
        order.state = CancelledState()

class ShippedState(OrderState):
    def pay(self, order: "Order") -> None:
        print("Already paid and shipped")

    def ship(self, order: "Order") -> None:
        print("Already shipped")

    def cancel(self, order: "Order") -> None:
        print("Cannot cancel — already shipped")

class CancelledState(OrderState):
    def pay(self, order: "Order") -> None:
        print("Order is cancelled")

    def ship(self, order: "Order") -> None:
        print("Order is cancelled")

    def cancel(self, order: "Order") -> None:
        print("Already cancelled")
```

### Step 3: Context holds the current state and delegates all state-dependent operations

```python
class Order:
    def __init__(self):
        self.state: OrderState = PendingState()

    def pay(self) -> None:
        self.state.pay(self)

    def ship(self) -> None:
        self.state.ship(self)

    def cancel(self) -> None:
        self.state.cancel(self)
```

### Step 4: Transition state inside state objects — not in the context

State transitions (`order.state = PaidState()`) happen inside state methods (Step 2),
not inside `Order`. This keeps the context thin and each state's transitions explicit.

### Step 5: Verify transitions by testing each state in isolation

```python
def test_pending_pay():
    order = Order()
    assert isinstance(order.state, PendingState)
    order.pay()
    assert isinstance(order.state, PaidState)

def test_shipped_cannot_cancel():
    order = Order()
    order.pay()
    order.ship()
    order.cancel()
    assert isinstance(order.state, ShippedState)   # state unchanged
```

## When NOT to Use

- **Simple states with few operations** — 2 states × 2 operations = 4 conditions. A simple `if/else` is cleaner than 2 state classes.
- **When transitions are purely data-driven** — if state transitions come from a config file or database, a data-driven state machine (a transition table) is more maintainable than code-based state objects.

## Common Mistakes

**Context that checks `isinstance(state, ...)` to make decisions.** This reintroduces the conditional logic the pattern eliminates. Delegate the decision to the state object.

**State objects that modify context fields directly.** If `PaidState` sets `order.paid_at = datetime.now()`, the state is coupled to the context's internals. Pass only what state objects need, or use events.

**Missing states in the state interface.** If one state handles `ship()` but another doesn't implement the interface method, calls fail unexpectedly. All states must implement all interface methods.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…