Skip to content
Low Level Design Mastery Logo
LowLevelDesign Mastery

State Pattern

Let objects change behavior when their state changes - no more giant if/else state machines!

State Pattern: Behavior That Changes with State

Section titled “State Pattern: Behavior That Changes with State”

Now let’s dive into the State Pattern - one of the most elegant behavioral design patterns that allows an object to alter its behavior when its internal state changes. The object will appear to change its class!

Imagine a vending machine. When it has no money inserted, pressing the dispense button does nothing. When money is inserted, pressing dispense gives you a product. When it’s out of stock, it refunds your money. The machine behaves differently based on its state. The State Pattern works the same way!

The State Pattern allows an object to change its behavior when its internal state changes. Instead of having giant if/else or switch statements checking the state, each state becomes its own class that knows how to handle requests for that state.

The State Pattern is useful when:

  1. Object behavior depends on state - Different actions based on internal state
  2. You have complex state-dependent logic - Many if/else checking state
  3. State transitions are well-defined - Clear rules for state changes
  4. You want to add new states easily - Without modifying existing code
  5. States should be explicit - Each state is a first-class object

What Happens If We Don’t Use State Pattern?

Section titled “What Happens If We Don’t Use State Pattern?”

Without the State Pattern, you might:

  • Giant switch/if-else statements - Checking state everywhere
  • Scattered state logic - Same checks in multiple methods
  • Hard to add states - Modify many places for new state
  • Violation of OCP - Need to modify existing code
  • Difficult to understand - State behavior spread across methods

Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the document transitions between states:

stateDiagram-v2
    [*] --> Draft: Create Document
    Draft --> Draft: edit()
    Draft --> Moderation: publish()
    Moderation --> Published: approve()
    Moderation --> Draft: reject()
    Published --> Draft: edit()
    Published --> [*]: archive()
    
    note right of Draft: Can edit freely
    note right of Moderation: Under review
    note right of Published: Live content
sequenceDiagram
    participant Client
    participant Document
    participant DraftState
    participant ModerationState
    participant PublishedState
    
    Client->>Document: new Document()
    Document->>DraftState: set initial state
    
    Client->>Document: edit("New content")
    activate Document
    Document->>DraftState: edit(document)
    DraftState->>DraftState: Update content
    DraftState-->>Document: Content updated
    deactivate Document
    
    Client->>Document: publish()
    activate Document
    Document->>DraftState: publish(document)
    DraftState->>Document: set_state(ModerationState)
    DraftState-->>Document: Sent to moderation
    deactivate Document
    
    Note over Client,PublishedState: State changed! Same method, different behavior
    
    Client->>Document: publish()
    activate Document
    Document->>ModerationState: publish(document)
    ModerationState->>Document: set_state(PublishedState)
    ModerationState-->>Document: Published!
    deactivate Document

You’re building a document management system where documents go through different states (Draft, Moderation, Published). Without State Pattern:

bad_document.py
# ❌ Without State Pattern - Giant if/else everywhere!
class Document:
def __init__(self, content: str):
self.content = content
self.state = "draft" # draft, moderation, published
def edit(self, new_content: str):
# Problem: Check state in every method!
if self.state == "draft":
self.content = new_content
print(f"📝 Content updated: {new_content}")
elif self.state == "moderation":
print("❌ Cannot edit - document is under moderation")
elif self.state == "published":
# Move back to draft for editing
self.content = new_content
self.state = "draft"
print(f"📝 Content updated, moved back to draft")
# What if we add a new state? Modify here!
def publish(self):
# Problem: Same state checks repeated!
if self.state == "draft":
self.state = "moderation"
print("📤 Document sent to moderation")
elif self.state == "moderation":
self.state = "published"
print("✅ Document published!")
elif self.state == "published":
print("ℹ️ Document is already published")
# What if we add a new state? Modify here too!
def reject(self):
# Problem: And here too!
if self.state == "draft":
print("❌ Cannot reject - document is not in moderation")
elif self.state == "moderation":
self.state = "draft"
print("🔙 Document rejected, back to draft")
elif self.state == "published":
print("❌ Cannot reject - document is already published")
# Every new method needs all state checks!
# Problems:
# - State checks in every method
# - Adding new state = modify ALL methods
# - Hard to understand state behavior
# - Violates Open/Closed Principle

Problems:

  • State checks scattered across every method
  • Adding a new state requires modifying ALL methods
  • Hard to understand what each state does
  • Violates Open/Closed Principle
classDiagram
    class DocumentState {
        <<interface>>
        +edit(doc, content) void
        +publish(doc) void
        +reject(doc) void
    }
    class DraftState {
        +edit(doc, content) void
        +publish(doc) void
        +reject(doc) void
    }
    class ModerationState {
        +edit(doc, content) void
        +publish(doc) void
        +reject(doc) void
    }
    class PublishedState {
        +edit(doc, content) void
        +publish(doc) void
        +reject(doc) void
    }
    class Document {
        -state: DocumentState
        -content: str
        +set_state(state) void
        +edit(content) void
        +publish() void
        +reject() void
    }
    
    DocumentState <|.. DraftState : implements
    DocumentState <|.. ModerationState : implements
    DocumentState <|.. PublishedState : implements
    Document --> DocumentState : delegates to
    
    note for DocumentState "Each state knows how\nto handle requests"
    note for Document "Context delegates\nto current state"
state_document.py
from abc import ABC, abstractmethod
# Step 1: Define the State interface
class DocumentState(ABC):
"""State interface - defines behavior for each state"""
@abstractmethod
def edit(self, document: 'Document', content: str) -> None:
"""Edit the document"""
pass
@abstractmethod
def publish(self, document: 'Document') -> None:
"""Publish the document"""
pass
@abstractmethod
def reject(self, document: 'Document') -> None:
"""Reject the document"""
pass
@property
@abstractmethod
def name(self) -> str:
"""Get state name"""
pass
# Step 2: Implement Concrete States
class DraftState(DocumentState):
"""Draft state - document can be freely edited"""
@property
def name(self) -> str:
return "Draft"
def edit(self, document: 'Document', content: str) -> None:
document._content = content
print(f"📝 Content updated: '{content}'")
def publish(self, document: 'Document') -> None:
document.set_state(ModerationState())
print("📤 Document sent to moderation")
def reject(self, document: 'Document') -> None:
print("❌ Cannot reject - document is not in moderation")
class ModerationState(DocumentState):
"""Moderation state - document is under review"""
@property
def name(self) -> str:
return "Moderation"
def edit(self, document: 'Document', content: str) -> None:
print("❌ Cannot edit - document is under moderation")
def publish(self, document: 'Document') -> None:
document.set_state(PublishedState())
print("✅ Document approved and published!")
def reject(self, document: 'Document') -> None:
document.set_state(DraftState())
print("🔙 Document rejected, back to draft")
class PublishedState(DocumentState):
"""Published state - document is live"""
@property
def name(self) -> str:
return "Published"
def edit(self, document: 'Document', content: str) -> None:
document._content = content
document.set_state(DraftState())
print(f"📝 Content updated: '{content}' - moved back to draft")
def publish(self, document: 'Document') -> None:
print("ℹ️ Document is already published")
def reject(self, document: 'Document') -> None:
print("❌ Cannot reject - document is already published")
# Step 3: Create the Context class
class Document:
"""Context - the document whose behavior changes based on state"""
def __init__(self, content: str):
self._content = content
self._state: DocumentState = DraftState() # Initial state
def set_state(self, state: DocumentState) -> None:
"""Change the document state"""
old_state = self._state.name
self._state = state
print(f" [State: {old_state}{state.name}]")
def edit(self, content: str) -> None:
"""Edit the document - behavior depends on state"""
print(f"\n📋 Attempting to edit in {self._state.name} state...")
self._state.edit(self, content)
def publish(self) -> None:
"""Publish the document - behavior depends on state"""
print(f"\n📋 Attempting to publish in {self._state.name} state...")
self._state.publish(self)
def reject(self) -> None:
"""Reject the document - behavior depends on state"""
print(f"\n📋 Attempting to reject in {self._state.name} state...")
self._state.reject(self)
@property
def content(self) -> str:
return self._content
@property
def state_name(self) -> str:
return self._state.name
# Step 4: Use the pattern
def main():
print("=" * 50)
print("Document Workflow (State Pattern)")
print("=" * 50)
# Create a new document (starts in Draft state)
doc = Document("Initial content")
print(f"📄 Created document in {doc.state_name} state")
# Edit in draft state - works!
doc.edit("Updated content in draft")
# Publish - moves to moderation
doc.publish()
# Try to edit in moderation - doesn't work!
doc.edit("Trying to edit in moderation")
# Approve (publish again) - moves to published
doc.publish()
# Try to publish again - already published
doc.publish()
# Edit published document - moves back to draft
doc.edit("Making changes to published doc")
# Publish again to go through the workflow
doc.publish() # Draft -> Moderation
doc.reject() # Moderation -> Draft
print("\n" + "=" * 50)
print("✅ State Pattern: Behavior changes with state!")
print("✅ No if/else chains - each state handles itself!")
if __name__ == "__main__":
main()

Real-World Software Example: Order Processing System

Section titled “Real-World Software Example: Order Processing System”

Now let’s see a realistic software example - an e-commerce order processing system with multiple states.

You’re building an order system where orders go through states: Pending → Paid → Shipped → Delivered (or Cancelled at various points). Without State Pattern:

bad_order.py
# ❌ Without State Pattern - State checks everywhere!
class Order:
def __init__(self, order_id: str, items: list, total: float):
self.order_id = order_id
self.items = items
self.total = total
self.state = "pending" # pending, paid, shipped, delivered, cancelled
def pay(self, amount: float):
if self.state == "pending":
if amount >= self.total:
self.state = "paid"
print(f"✅ Order {self.order_id} paid!")
else:
print(f"❌ Insufficient payment: ${amount} < ${self.total}")
elif self.state == "paid":
print("ℹ️ Order already paid")
elif self.state == "shipped":
print("ℹ️ Order already shipped")
elif self.state == "delivered":
print("ℹ️ Order already delivered")
elif self.state == "cancelled":
print("❌ Cannot pay - order is cancelled")
def ship(self, tracking_number: str):
if self.state == "pending":
print("❌ Cannot ship - order not paid")
elif self.state == "paid":
self.tracking_number = tracking_number
self.state = "shipped"
print(f"📦 Order {self.order_id} shipped! Tracking: {tracking_number}")
elif self.state == "shipped":
print("ℹ️ Order already shipped")
elif self.state == "delivered":
print("ℹ️ Order already delivered")
elif self.state == "cancelled":
print("❌ Cannot ship - order is cancelled")
def deliver(self):
if self.state == "pending":
print("❌ Cannot deliver - order not paid")
elif self.state == "paid":
print("❌ Cannot deliver - order not shipped")
elif self.state == "shipped":
self.state = "delivered"
print(f"✅ Order {self.order_id} delivered!")
elif self.state == "delivered":
print("ℹ️ Order already delivered")
elif self.state == "cancelled":
print("❌ Cannot deliver - order is cancelled")
def cancel(self):
if self.state == "pending":
self.state = "cancelled"
print(f"❌ Order {self.order_id} cancelled")
elif self.state == "paid":
self.state = "cancelled"
print(f"❌ Order {self.order_id} cancelled - refund initiated")
elif self.state == "shipped":
print("❌ Cannot cancel - order already shipped")
elif self.state == "delivered":
print("❌ Cannot cancel - order already delivered")
elif self.state == "cancelled":
print("ℹ️ Order already cancelled")
# Problems:
# - 5 states × 4 methods = 20 if branches!
# - Adding new state = modify ALL methods
# - Adding new method = add ALL state checks

Problems:

  • 5 states × 4 methods = 20 if-else branches!
  • Adding a new state requires modifying ALL methods
  • State behavior scattered across methods
  • Very error-prone and hard to maintain
stateDiagram-v2
    [*] --> Pending: Create Order
    Pending --> Paid: pay()
    Pending --> Cancelled: cancel()
    Paid --> Shipped: ship()
    Paid --> Cancelled: cancel() + refund
    Shipped --> Delivered: deliver()
    Delivered --> [*]: Complete
    Cancelled --> [*]: Complete
    
    note right of Pending: Awaiting payment
    note right of Paid: Ready to ship
    note right of Shipped: In transit
    note right of Delivered: Complete!
    note right of Cancelled: Order cancelled
state_order.py
from abc import ABC, abstractmethod
from typing import Optional
from dataclasses import dataclass, field
from datetime import datetime
# Step 1: Define the State interface
class OrderState(ABC):
"""State interface for order states"""
@abstractmethod
def pay(self, order: 'Order', amount: float) -> None:
pass
@abstractmethod
def ship(self, order: 'Order', tracking_number: str) -> None:
pass
@abstractmethod
def deliver(self, order: 'Order') -> None:
pass
@abstractmethod
def cancel(self, order: 'Order') -> None:
pass
@property
@abstractmethod
def name(self) -> str:
pass
# Step 2: Implement Concrete States
class PendingState(OrderState):
"""Pending state - awaiting payment"""
@property
def name(self) -> str:
return "Pending"
def pay(self, order: 'Order', amount: float) -> None:
if amount >= order.total:
order.amount_paid = amount
order.set_state(PaidState())
print(f"✅ Payment of ${amount:.2f} received!")
else:
print(f"❌ Insufficient payment: ${amount:.2f} < ${order.total:.2f}")
def ship(self, order: 'Order', tracking_number: str) -> None:
print("❌ Cannot ship - order not paid yet")
def deliver(self, order: 'Order') -> None:
print("❌ Cannot deliver - order not paid yet")
def cancel(self, order: 'Order') -> None:
order.set_state(CancelledState())
print("🚫 Order cancelled")
class PaidState(OrderState):
"""Paid state - ready to ship"""
@property
def name(self) -> str:
return "Paid"
def pay(self, order: 'Order', amount: float) -> None:
print("ℹ️ Order already paid")
def ship(self, order: 'Order', tracking_number: str) -> None:
order.tracking_number = tracking_number
order.shipped_at = datetime.now()
order.set_state(ShippedState())
print(f"📦 Order shipped! Tracking: {tracking_number}")
def deliver(self, order: 'Order') -> None:
print("❌ Cannot deliver - order not shipped yet")
def cancel(self, order: 'Order') -> None:
order.set_state(CancelledState())
print(f"🚫 Order cancelled - refund of ${order.amount_paid:.2f} initiated")
class ShippedState(OrderState):
"""Shipped state - in transit"""
@property
def name(self) -> str:
return "Shipped"
def pay(self, order: 'Order', amount: float) -> None:
print("ℹ️ Order already paid")
def ship(self, order: 'Order', tracking_number: str) -> None:
print("ℹ️ Order already shipped")
def deliver(self, order: 'Order') -> None:
order.delivered_at = datetime.now()
order.set_state(DeliveredState())
print("✅ Order delivered successfully!")
def cancel(self, order: 'Order') -> None:
print("❌ Cannot cancel - order already shipped. Please initiate return after delivery.")
class DeliveredState(OrderState):
"""Delivered state - order complete"""
@property
def name(self) -> str:
return "Delivered"
def pay(self, order: 'Order', amount: float) -> None:
print("ℹ️ Order already paid and delivered")
def ship(self, order: 'Order', tracking_number: str) -> None:
print("ℹ️ Order already delivered")
def deliver(self, order: 'Order') -> None:
print("ℹ️ Order already delivered")
def cancel(self, order: 'Order') -> None:
print("❌ Cannot cancel - order already delivered. Please initiate return.")
class CancelledState(OrderState):
"""Cancelled state - order terminated"""
@property
def name(self) -> str:
return "Cancelled"
def pay(self, order: 'Order', amount: float) -> None:
print("❌ Cannot pay - order is cancelled")
def ship(self, order: 'Order', tracking_number: str) -> None:
print("❌ Cannot ship - order is cancelled")
def deliver(self, order: 'Order') -> None:
print("❌ Cannot deliver - order is cancelled")
def cancel(self, order: 'Order') -> None:
print("ℹ️ Order already cancelled")
# Step 3: Create the Context class
@dataclass
class OrderItem:
"""Item in an order"""
name: str
price: float
quantity: int
class Order:
"""Context - the order whose behavior changes based on state"""
def __init__(self, order_id: str, items: list[OrderItem]):
self.order_id = order_id
self.items = items
self.total = sum(item.price * item.quantity for item in items)
self.amount_paid: float = 0
self.tracking_number: Optional[str] = None
self.shipped_at: Optional[datetime] = None
self.delivered_at: Optional[datetime] = None
self._state: OrderState = PendingState()
self.created_at = datetime.now()
def set_state(self, state: OrderState) -> None:
"""Change order state"""
old_state = self._state.name
self._state = state
print(f" [Order {self.order_id}: {old_state}{state.name}]")
def pay(self, amount: float) -> None:
"""Pay for the order"""
print(f"\n💳 Processing payment of ${amount:.2f}...")
self._state.pay(self, amount)
def ship(self, tracking_number: str) -> None:
"""Ship the order"""
print(f"\n📦 Processing shipment...")
self._state.ship(self, tracking_number)
def deliver(self) -> None:
"""Mark order as delivered"""
print(f"\n🚚 Processing delivery...")
self._state.deliver(self)
def cancel(self) -> None:
"""Cancel the order"""
print(f"\n🚫 Processing cancellation...")
self._state.cancel(self)
def get_status(self) -> str:
"""Get order status summary"""
return f"""
Order: {self.order_id}
Status: {self._state.name}
Items: {len(self.items)}
Total: ${self.total:.2f}
Paid: ${self.amount_paid:.2f}
Tracking: {self.tracking_number or 'N/A'}
"""
# Step 4: Use the pattern
def main():
print("=" * 60)
print("E-Commerce Order Processing (State Pattern)")
print("=" * 60)
# Create an order
items = [
OrderItem("Laptop", 999.99, 1),
OrderItem("Mouse", 29.99, 2),
]
order = Order("ORD-001", items)
print(f"📝 Created order {order.order_id}")
print(f" Total: ${order.total:.2f}")
# Try to ship before paying - should fail
order.ship("TRACK123")
# Pay for the order
order.pay(500) # Insufficient
order.pay(1059.97) # Exact amount
# Try to pay again - already paid
order.pay(100)
# Ship the order
order.ship("TRACK-12345-XYZ")
# Try to cancel after shipping - should fail
order.cancel()
# Deliver the order
order.deliver()
# Print final status
print("\n" + "=" * 60)
print("Final Order Status:")
print(order.get_status())
# Demonstrate cancellation flow
print("=" * 60)
print("Testing Cancellation Flow:")
print("=" * 60)
order2 = Order("ORD-002", [OrderItem("Keyboard", 79.99, 1)])
print(f"📝 Created order {order2.order_id}")
order2.pay(79.99)
order2.cancel() # Should work - refund initiated
print("\n✅ State Pattern: Clean state-based behavior!")
print("✅ Adding new state = Just one new class!")
if __name__ == "__main__":
main()

There are different ways to implement the State Pattern:

When states are stateless, they can be singletons:

singleton_state.py
# Singleton States - when states are stateless
class DraftState:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def publish(self, document):
document.set_state(ModerationState())
# Usage - always same instance
state1 = DraftState()
state2 = DraftState()
assert state1 is state2 # True - same instance

Pros: Memory efficient, no unnecessary object creation
Cons: States can’t hold instance-specific data

Track state history for auditing:

state_history.py
# State with history tracking
class Context:
def __init__(self):
self._state = InitialState()
self._state_history = []
def set_state(self, state):
# Record state transition
self._state_history.append({
'from': self._state.name,
'to': state.name,
'timestamp': datetime.now()
})
self._state = state
def get_history(self):
return self._state_history

States can have sub-states:

hierarchical_state.py
# Hierarchical states - states with sub-states
class ShippedState(OrderState):
"""Parent state with sub-states"""
def __init__(self, sub_state: str = "in_transit"):
self.sub_state = sub_state # in_transit, out_for_delivery
def update_location(self, order, location):
if location == "local_hub":
self.sub_state = "out_for_delivery"
print(f"📍 Package at local hub - out for delivery!")

Use State Pattern when:

Object behavior depends on state - Different actions for different states
You have many state-checking conditionals - if/else chains everywhere
State transitions are well-defined - Clear rules for state changes
You want to add states easily - Without modifying existing code
States should be explicit - First-class state objects

Don’t use State Pattern when:

Few states with simple transitions - Direct if/else might be clearer
State doesn’t affect behavior - Just data, not behavior
States are not well-defined - Unclear transitions
Over-engineering - Don’t add complexity for 2-3 simple states


context_reference.py
# ❌ Bad: State holds permanent context reference
class BadState:
def __init__(self, context):
self.context = context # Bad: Permanent reference
def do_something(self):
self.context.transition()
# ✅ Good: Context passed as parameter
class GoodState:
def do_something(self, context): # Good: Passed as parameter
context.transition()

Mistake 2: Context Knowing About All Concrete States

Section titled “Mistake 2: Context Knowing About All Concrete States”
context_knows_states.py
# ❌ Bad: Context creates specific states
class BadContext:
def handle(self):
if some_condition:
self.state = DraftState() # Bad: Context knows concrete states
else:
self.state = PublishedState()
# ✅ Good: States handle their own transitions
class GoodContext:
def handle(self):
self.state.handle(self) # Good: State decides next state

Mistake 3: Missing Default Behavior in States

Section titled “Mistake 3: Missing Default Behavior in States”
default_behavior.py
# ❌ Bad: No default behavior - need to implement everything
class BadState(ABC):
@abstractmethod
def method1(self, ctx): pass
@abstractmethod
def method2(self, ctx): pass
@abstractmethod
def method3(self, ctx): pass
# Every state must implement ALL methods!
# ✅ Good: Base state with default behavior
class BaseState(ABC):
def method1(self, ctx):
print("Operation not available in this state")
def method2(self, ctx):
print("Operation not available in this state")
# States only override what they need

  1. No if/else chains - Each state handles its own behavior
  2. Easy to add states - Just create a new state class
  3. Single Responsibility - Each state has one job
  4. Open/Closed Principle - Add states without modifying existing code
  5. Explicit state machine - State diagram maps to code
  6. Testable - Each state can be tested independently

State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. The object appears to change its class.

  • Eliminate if/else chains - State checks spread everywhere
  • Easy to add states - New state = new class
  • Clear state behavior - All behavior in one place
  • Explicit transitions - States control transitions
  • Follow Open/Closed Principle
  1. Define State interface - Methods for all state behaviors
  2. Create Concrete States - Each state implements interface
  3. Create Context - Holds current state, delegates to it
  4. States change Context state - Transitions happen in states
  5. Client uses Context - Doesn’t know about states
Context → State Interface → Concrete States
  • State - Interface for state behavior
  • Concrete State - Specific state implementation
  • Context - Object whose behavior changes
  • Client - Uses context, unaware of states
class State(ABC):
@abstractmethod
def handle(self, context): pass
class ConcreteStateA(State):
def handle(self, context):
print("State A behavior")
context.set_state(ConcreteStateB())
class Context:
def __init__(self):
self._state = ConcreteStateA()
def set_state(self, state):
self._state = state
def request(self):
self._state.handle(self)

✅ Object behavior depends on state
✅ Many if/else checking state
✅ Well-defined state transitions
✅ Need to add states easily
✅ States should be explicit

❌ Few simple states
❌ State doesn’t affect behavior
❌ Unclear transitions
❌ Over-engineering

  • State Pattern = Behavior changes with state
  • State = Encapsulates state-specific behavior
  • Context = Delegates to current state
  • Benefit = No if/else, easy to extend
  • Principle = Open for extension, closed for modification

What to say:

“State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. Instead of having if/else chains checking state, each state becomes its own class that handles behavior for that state. The object appears to change its class.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of eliminating conditionals
  • Indicates you can explain concepts clearly

Must discuss:

  • State - Object changes behavior based on INTERNAL state, automatic
  • Strategy - Client chooses behavior EXTERNALLY, explicit
  • Key difference - Who controls the change and why

Example to give:

“State Pattern is like a traffic light that automatically changes behavior based on its internal state - red means stop, green means go. Strategy Pattern is like choosing a route on GPS - YOU explicitly choose the algorithm. With State, transitions happen automatically based on internal logic. With Strategy, you explicitly set which algorithm to use.”

Benefits to mention:

  • No if/else chains - Clean, maintainable code
  • Easy to add states - Just create new class
  • Single Responsibility - Each state handles its behavior
  • Open/Closed Principle - Add without modifying
  • Explicit state machine - Code mirrors state diagram

Trade-offs to acknowledge:

  • More classes - Each state is a class
  • Overkill for simple states - 2-3 states might not need it
  • Can be complex - Many states with many methods

Q: “How does State Pattern differ from Strategy Pattern?”

A:

“Both encapsulate behavior in separate classes, but State changes behavior based on internal state automatically, while Strategy lets the client explicitly choose the algorithm. State objects typically know about other states and handle transitions. Strategy objects are usually stateless and interchangeable. State is about ‘what state am I in?’, Strategy is about ‘which algorithm should I use?’”

Q: “Where would you use State Pattern in a real system?”

A:

“I’d use State Pattern for order processing systems (pending, paid, shipped, delivered), document workflows (draft, review, published), connection handling (connecting, connected, disconnected), or UI components (enabled, disabled, loading). Any system where objects go through well-defined states with different behavior.”

Q: “How do you handle state transitions?”

A:

“State transitions can be handled in two ways: 1) States handle their own transitions - each state knows what the next state should be and calls context.setState(). 2) Context handles transitions - context decides next state based on rules. The first approach is cleaner because each state encapsulates its transition logic.”

Before your interview, make sure you can:

  • Define State Pattern clearly in one sentence
  • Explain when to use it (with examples)
  • Compare State vs Strategy Pattern
  • Implement State Pattern from scratch
  • List benefits and trade-offs
  • Connect to SOLID principles
  • Identify when NOT to use it
  • Give 2-3 real-world examples
  • Discuss state transition approaches
  • Draw a state diagram for an example

Remember: State Pattern is about letting objects change behavior when their state changes - no more giant if/else state machines! 🔄