# State Pattern

## Problem

A document has a lifecycle: Draft → Review → Published. Each state allows different operations — only drafts can be edited, only reviews can be approved or rejected, and published documents are read-only. Implementing this with `if/else` chains on a state variable scatters state logic everywhere and makes adding new states error-prone.

## Solution

Encapsulate each state as its own class that implements a common interface. The Document (context) delegates behavior to the current state object. State transitions replace the context's state object rather than changing a variable.

**Key participants:**
- **Context** — the Document, holds a reference to the current State
- **State** — interface declaring all possible actions
- **ConcreteState** — DraftState, ReviewState, PublishedState with specific behavior

## When to Use

- An object's behavior depends on its state, and it changes state at runtime
- State-specific behavior is scattered across many conditionals
- State transitions follow defined rules (state machine)
- You want to make state transitions explicit and testable

## When to Avoid

- Only 2-3 states with trivial behavior — a simple enum + switch is clearer
- State transitions are not well-defined or are highly dynamic
- The overhead of state classes outweighs the benefit (rare transitions)

## Pseudocode

```
INTERFACE State:
    method edit(doc, content)
    method submit(doc)
    method approve(doc)
    method reject(doc)
    method publish(doc)

CLASS DraftState IMPLEMENTS State:
    method edit(doc, content):
        doc.content = content
        PRINT "Draft edited"
    method submit(doc):
        doc.state = ReviewState()
        PRINT "Submitted for review"
    method approve(doc):
        PRINT "Cannot approve a draft"
    // ... etc

CLASS Document:
    state = DraftState()
    content = ""

    method edit(content): state.edit(self, content)
    method submit():      state.submit(self)
    method approve():     state.approve(self)
```

## Python

```python
from __future__ import annotations
from abc import ABC, abstractmethod


class State(ABC):
    @property
    @abstractmethod
    def name(self) -> str:
        pass

    def edit(self, doc: Document, content: str) -> None:
        print(f"  [{self.name}] Cannot edit in this state")

    def submit(self, doc: Document) -> None:
        print(f"  [{self.name}] Cannot submit in this state")

    def approve(self, doc: Document) -> None:
        print(f"  [{self.name}] Cannot approve in this state")

    def reject(self, doc: Document) -> None:
        print(f"  [{self.name}] Cannot reject in this state")

    def publish(self, doc: Document) -> None:
        print(f"  [{self.name}] Cannot publish in this state")


class DraftState(State):
    @property
    def name(self) -> str:
        return "Draft"

    def edit(self, doc: Document, content: str) -> None:
        doc.content = content
        print(f"  [{self.name}] Content updated: '{content}'")

    def submit(self, doc: Document) -> None:
        doc.state = ReviewState()
        print(f"  [{self.name}] -> Submitted for review")


class ReviewState(State):
    @property
    def name(self) -> str:
        return "Review"

    def approve(self, doc: Document) -> None:
        doc.state = PublishedState()
        print(f"  [{self.name}] -> Approved! Document published")

    def reject(self, doc: Document) -> None:
        doc.state = DraftState()
        print(f"  [{self.name}] -> Rejected. Back to draft")


class PublishedState(State):
    @property
    def name(self) -> str:
        return "Published"

    def publish(self, doc: Document) -> None:
        print(f"  [{self.name}] Document is already published")


class Document:
    def __init__(self, title: str) -> None:
        self.title = title
        self.content = ""
        self.state: State = DraftState()

    def edit(self, content: str) -> None:
        self.state.edit(self, content)

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

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

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

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

    def status(self) -> None:
        print(f"  Document '{self.title}' | State: {self.state.name} | Content: '{self.content}'")


def main() -> None:
    doc = Document("Design Spec")
    doc.status()

    print("\n--- Edit the draft ---")
    doc.edit("Initial design for the new API")
    doc.status()

    print("\n--- Try to approve while in Draft (should fail) ---")
    doc.approve()

    print("\n--- Submit for review ---")
    doc.submit()
    doc.status()

    print("\n--- Try to edit while in Review (should fail) ---")
    doc.edit("Sneaky edit")

    print("\n--- Reject the document ---")
    doc.reject()
    doc.status()

    print("\n--- Edit and resubmit ---")
    doc.edit("Revised design for the new API")
    doc.submit()

    print("\n--- Approve ---")
    doc.approve()
    doc.status()

    print("\n--- Try to edit published document (should fail) ---")
    doc.edit("Another edit")

    print("\n--- Try to publish again ---")
    doc.publish()


if __name__ == "__main__":
    main()
```

**Output:**
```
  Document 'Design Spec' | State: Draft | Content: ''

--- Edit the draft ---
  [Draft] Content updated: 'Initial design for the new API'
  Document 'Design Spec' | State: Draft | Content: 'Initial design for the new API'

--- Try to approve while in Draft (should fail) ---
  [Draft] Cannot approve in this state

--- Submit for review ---
  [Draft] -> Submitted for review
  Document 'Design Spec' | State: Review | Content: 'Initial design for the new API'

--- Try to edit while in Review (should fail) ---
  [Review] Cannot edit in this state

--- Reject the document ---
  [Review] -> Rejected. Back to draft
  Document 'Design Spec' | State: Draft | Content: 'Initial design for the new API'

--- Edit and resubmit ---
  [Draft] Content updated: 'Revised design for the new API'
  [Draft] -> Submitted for review

--- Approve ---
  [Review] -> Approved! Document published
  Document 'Design Spec' | State: Published | Content: 'Revised design for the new API'

--- Try to edit published document (should fail) ---
  [Published] Cannot edit in this state

--- Try to publish again ---
  [Published] Document is already published
```

## Go

```go
package main

import "fmt"

type State interface {
	Name() string
	Edit(doc *Document, content string)
	Submit(doc *Document)
	Approve(doc *Document)
	Reject(doc *Document)
	Publish(doc *Document)
}

// --- Draft ---
type DraftState struct{}

func (s *DraftState) Name() string { return "Draft" }
func (s *DraftState) Edit(doc *Document, content string) {
	doc.Content = content
	fmt.Printf("  [%s] Content updated: '%s'\n", s.Name(), content)
}
func (s *DraftState) Submit(doc *Document) {
	doc.SetState(&ReviewState{})
	fmt.Printf("  [%s] -> Submitted for review\n", s.Name())
}
func (s *DraftState) Approve(doc *Document) {
	fmt.Printf("  [%s] Cannot approve in this state\n", s.Name())
}
func (s *DraftState) Reject(doc *Document) {
	fmt.Printf("  [%s] Cannot reject in this state\n", s.Name())
}
func (s *DraftState) Publish(doc *Document) {
	fmt.Printf("  [%s] Cannot publish in this state\n", s.Name())
}

// --- Review ---
type ReviewState struct{}

func (s *ReviewState) Name() string { return "Review" }
func (s *ReviewState) Edit(doc *Document, content string) {
	fmt.Printf("  [%s] Cannot edit in this state\n", s.Name())
}
func (s *ReviewState) Submit(doc *Document) {
	fmt.Printf("  [%s] Cannot submit in this state\n", s.Name())
}
func (s *ReviewState) Approve(doc *Document) {
	doc.SetState(&PublishedState{})
	fmt.Printf("  [%s] -> Approved! Document published\n", s.Name())
}
func (s *ReviewState) Reject(doc *Document) {
	doc.SetState(&DraftState{})
	fmt.Printf("  [%s] -> Rejected. Back to draft\n", s.Name())
}
func (s *ReviewState) Publish(doc *Document) {
	fmt.Printf("  [%s] Cannot publish in this state\n", s.Name())
}

// --- Published ---
type PublishedState struct{}

func (s *PublishedState) Name() string { return "Published" }
func (s *PublishedState) Edit(doc *Document, content string) {
	fmt.Printf("  [%s] Cannot edit in this state\n", s.Name())
}
func (s *PublishedState) Submit(doc *Document) {
	fmt.Printf("  [%s] Cannot submit in this state\n", s.Name())
}
func (s *PublishedState) Approve(doc *Document) {
	fmt.Printf("  [%s] Cannot approve in this state\n", s.Name())
}
func (s *PublishedState) Reject(doc *Document) {
	fmt.Printf("  [%s] Cannot reject in this state\n", s.Name())
}
func (s *PublishedState) Publish(doc *Document) {
	fmt.Printf("  [%s] Document is already published\n", s.Name())
}

// --- Document ---
type Document struct {
	Title   string
	Content string
	state   State
}

func NewDocument(title string) *Document {
	return &Document{Title: title, state: &DraftState{}}
}

func (d *Document) SetState(s State)            { d.state = s }
func (d *Document) Edit(content string)         { d.state.Edit(d, content) }
func (d *Document) Submit()                     { d.state.Submit(d) }
func (d *Document) Approve()                    { d.state.Approve(d) }
func (d *Document) Reject()                     { d.state.Reject(d) }
func (d *Document) Publish()                    { d.state.Publish(d) }
func (d *Document) Status() {
	fmt.Printf("  Document '%s' | State: %s | Content: '%s'\n",
		d.Title, d.state.Name(), d.Content)
}

func main() {
	doc := NewDocument("Design Spec")
	doc.Status()

	fmt.Println("\n--- Edit the draft ---")
	doc.Edit("Initial design for the new API")
	doc.Status()

	fmt.Println("\n--- Try to approve while in Draft ---")
	doc.Approve()

	fmt.Println("\n--- Submit for review ---")
	doc.Submit()
	doc.Status()

	fmt.Println("\n--- Try to edit while in Review ---")
	doc.Edit("Sneaky edit")

	fmt.Println("\n--- Reject ---")
	doc.Reject()
	doc.Status()

	fmt.Println("\n--- Edit and resubmit ---")
	doc.Edit("Revised design for the new API")
	doc.Submit()

	fmt.Println("\n--- Approve ---")
	doc.Approve()
	doc.Status()

	fmt.Println("\n--- Try to edit published ---")
	doc.Edit("Another edit")

	fmt.Println("\n--- Try to publish again ---")
	doc.Publish()
}
```

**Output:**
```
  Document 'Design Spec' | State: Draft | Content: ''

--- Edit the draft ---
  [Draft] Content updated: 'Initial design for the new API'
  Document 'Design Spec' | State: Draft | Content: 'Initial design for the new API'

--- Try to approve while in Draft ---
  [Draft] Cannot approve in this state

--- Submit for review ---
  [Draft] -> Submitted for review
  Document 'Design Spec' | State: Review | Content: 'Initial design for the new API'

--- Try to edit while in Review ---
  [Review] Cannot edit in this state

--- Reject ---
  [Review] -> Rejected. Back to draft
  Document 'Design Spec' | State: Draft | Content: 'Initial design for the new API'

--- Edit and resubmit ---
  [Draft] Content updated: 'Revised design for the new API'
  [Draft] -> Submitted for review

--- Approve ---
  [Review] -> Approved! Document published
  Document 'Design Spec' | State: Published | Content: 'Revised design for the new API'

--- Try to edit published ---
  [Published] Cannot edit in this state

--- Try to publish again ---
  [Published] Document is already published
```

## JavaScript

```javascript
class DraftState {
  get name() { return "Draft"; }

  edit(doc, content) {
    doc.content = content;
    console.log(`  [${this.name}] Content updated: '${content}'`);
  }
  submit(doc) {
    doc.state = new ReviewState();
    console.log(`  [${this.name}] -> Submitted for review`);
  }
  approve(doc) { console.log(`  [${this.name}] Cannot approve in this state`); }
  reject(doc)  { console.log(`  [${this.name}] Cannot reject in this state`); }
  publish(doc) { console.log(`  [${this.name}] Cannot publish in this state`); }
}

class ReviewState {
  get name() { return "Review"; }

  edit(doc, content) { console.log(`  [${this.name}] Cannot edit in this state`); }
  submit(doc)        { console.log(`  [${this.name}] Cannot submit in this state`); }
  approve(doc) {
    doc.state = new PublishedState();
    console.log(`  [${this.name}] -> Approved! Document published`);
  }
  reject(doc) {
    doc.state = new DraftState();
    console.log(`  [${this.name}] -> Rejected. Back to draft`);
  }
  publish(doc) { console.log(`  [${this.name}] Cannot publish in this state`); }
}

class PublishedState {
  get name() { return "Published"; }

  edit(doc, content) { console.log(`  [${this.name}] Cannot edit in this state`); }
  submit(doc)        { console.log(`  [${this.name}] Cannot submit in this state`); }
  approve(doc)       { console.log(`  [${this.name}] Cannot approve in this state`); }
  reject(doc)        { console.log(`  [${this.name}] Cannot reject in this state`); }
  publish(doc)       { console.log(`  [${this.name}] Document is already published`); }
}

class Document {
  constructor(title) {
    this.title = title;
    this.content = "";
    this.state = new DraftState();
  }

  edit(content) { this.state.edit(this, content); }
  submit()      { this.state.submit(this); }
  approve()     { this.state.approve(this); }
  reject()      { this.state.reject(this); }
  publish()     { this.state.publish(this); }

  status() {
    console.log(`  Document '${this.title}' | State: ${this.state.name} | Content: '${this.content}'`);
  }
}

// --- Demo ---
const doc = new Document("Design Spec");
doc.status();

console.log("\n--- Edit the draft ---");
doc.edit("Initial design for the new API");
doc.status();

console.log("\n--- Try to approve while in Draft ---");
doc.approve();

console.log("\n--- Submit for review ---");
doc.submit();
doc.status();

console.log("\n--- Try to edit while in Review ---");
doc.edit("Sneaky edit");

console.log("\n--- Reject ---");
doc.reject();
doc.status();

console.log("\n--- Edit and resubmit ---");
doc.edit("Revised design for the new API");
doc.submit();

console.log("\n--- Approve ---");
doc.approve();
doc.status();

console.log("\n--- Try to edit published ---");
doc.edit("Another edit");

console.log("\n--- Try to publish again ---");
doc.publish();
```

**Output:**
```
  Document 'Design Spec' | State: Draft | Content: ''

--- Edit the draft ---
  [Draft] Content updated: 'Initial design for the new API'
  Document 'Design Spec' | State: Draft | Content: 'Initial design for the new API'

--- Try to approve while in Draft ---
  [Draft] Cannot approve in this state

--- Submit for review ---
  [Draft] -> Submitted for review
  Document 'Design Spec' | State: Review | Content: 'Initial design for the new API'

--- Try to edit while in Review ---
  [Review] Cannot edit in this state

--- Reject ---
  [Review] -> Rejected. Back to draft
  Document 'Design Spec' | State: Draft | Content: 'Initial design for the new API'

--- Edit and resubmit ---
  [Draft] Content updated: 'Revised design for the new API'
  [Draft] -> Submitted for review

--- Approve ---
  [Review] -> Approved! Document published
  Document 'Design Spec' | State: Published | Content: 'Revised design for the new API'

--- Try to edit published ---
  [Published] Cannot edit in this state

--- Try to publish again ---
  [Published] Document is already published
```

## Related Patterns

- **Strategy** — Both use interchangeable object implementations. Strategy is chosen by client and stays fixed; State transitions automatically based on conditions.
- **Observer** — State transitions should emit events via Observer so other components react to state changes.
- **Command** — Commands often trigger State transitions (submit order → Pending; pay → Confirmed).
- **Memento** — Can snapshot state before transition, enabling rollback to previous state.
- **Flyweight** — State objects are often shared if they carry no instance-specific data.
