StudyDSA logoStudyDSA

Command Palette

Search for a command to run...

Sign InSign Up
Sign Up

Data StructuresAlgorithmsBig-O NotationDesign PatternsSystem DesignMachine LearningPhysicsRoboticsAI Research

Factory MethodAbstract FactoryBuilderPrototypeSingletonAdapterBridgeCompositeDecoratorFacadeFlyweightProxyChain of ResponsibilityCommandIteratorMediatorMementoObserverStateStrategyTemplate MethodVisitor

Definition
StudyDSA

Where complexity meets clarity.
By Armas Zarra.

Topics

  • Data Structures
  • Algorithms
  • Big-O Notation
  • Robotics
  • AI Research
  • Machine Learning

Practice

  • Blind 75
  • LeetCode 75
  • NeetCode 150

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Armas Films LLC

Design PatternsState

State

A behavioral pattern that promotes each mode of an object to its own class, with the modes deciding for themselves when the object changes modes.

Definition

Some objects are secretly several objects taking turns: a document behaves one way as a draft, another in moderation, a third once published, and code that models this with a mode field ends up branching on that field in every method. State is the Gang of Four behavioral pattern that lets an object alter its behavior when its internal state changes, appearing to change its class, the framing the standard reference preserves from the original catalog.


The State pattern is really just polymorphism replacing a switch. The mode field becomes a mode object, each branch of the old ladder becomes a class, and dispatch does the selecting that the conditionals used to do. What makes it more than a refactor is the second move: the mode objects decide for themselves when the document changes modes.

The Ladder in Every Method

Here's the mode-field version of a publishing workflow, three states and the classic ladder. Read it fairly, because for a machine this small it's honestly fine, and the Rust tab is even compiler-checked. The problem this page solves is what happens to this shape under growth:

Count the ladders: one in publish(), a second in render(), and a third in every future method that cares about state, each a copy that must agree with all the others about what the modes mean. Add a scheduled state and you edit every method. Add a delete() method and you rebuild the whole ladder again. The growth is multiplicative, methods times states, and mode-field code pays it in merge conflicts.

States That Choose Their Successor

The pattern version gives each mode a class implementing the full interface, and the Document becomes a thin host delegating to whichever state it currently holds. The detail to watch is who calls set_state(): the states themselves, from inside their own methods. The example calls publish() four times, twice without admin rights, once with, once after the end. Predict all four outputs before reading on:

The outputs run sent to moderation, then moderation requires an admin, then published, then already published, and the Document class contains not one conditional. The second call is the pattern in miniature: Moderation checked the admin flag, decided not to transition, and the document stayed put, a decision made entirely inside the state that owned it. Adding a Scheduled mode is now one new class and one edited predecessor, not a sweep through every method.

Who Does What

Three roles. The Context (Document) holds a reference to the current state object, delegates state-dependent work to it, and exposes the setter states use to swap themselves out. The State interface declares the mode-dependent operations, and ConcreteStates (Draft, Moderation, Published) implement them, holding a back-reference to the context precisely so they can fire transitions.


The pattern works like a traffic light: the same lamp housing behaves as three different signals, and the light changes itself, green deciding when yellow takes over, no external operator flipping it. The analogy's limit is its rigidity, since a light cycles on a fixed loop while real state machines branch on events and conditions, the way moderation's exit depended on an admin check. Hold onto the self-changing part though, because it's about to be the entire difference between this pattern and its identical twin.

State: Step-by-Step

Installing the pattern is four moves:

1. Promote the Mode Field to a Mode Object

The string "draft" becomes a Draft object, and comparisons against the field become method calls on it. Dispatch replaces the ladder, which is the whole mechanical content of the pattern.

2. Give Every Mode the Full Interface

Each state implements every operation, even when the answer is a refusal like already published. A mode that throws NotImplemented on half its methods is a ladder that moved house without unpacking.

3. Let States Appoint Their Successors

Draft.publish() calls doc.set_state(Moderation()) itself, which is the pattern's defining move: the transition logic lives with the state that knows when to leave, not in the context or the caller.

4. Keep the Context a Thin Host

Document holds the current state, exposes set_state(), and delegates everything else in one line each. The moment the context grows its own conditionals on the state, the ladder is regrowing in the hallway.
State vs Strategy, Settled by Who Swaps

You might think State and Strategy are one pattern with two names, and the class diagrams are genuinely identical: a context holding an interface, concrete implementations behind it. The difference is who changes the object, and the standard reference draws the line exactly there: states know about each other and alter the context's state at will, while strategies are independent and never touch the context's choice. In Strategy, the client picks the algorithm and the algorithm computes. In State, the document was constructed in Draft and every transition after that was an inside job.


A quick test for code you're reading: find every call to the setter. All the calls coming from client code means Strategy, calls coming from the concrete implementations themselves means State, and both at once means someone merged the patterns and inherited both sets of bugs. The swappability isn't the distinction, since strategies can be swapped at runtime too. The distinction is whose hand is on the switch.

The Go and Rust Versions

Go follows the classic shape: a State interface, stateless concrete structs, and a context delegating through its current state, all visible in the code tab. With no inheritance to lean on, the back-reference arrives as an explicit doc parameter on every state method, which has the side benefit of making the transition mechanics impossible to miss in review.


Rust offers two answers, and the code tabs split them deliberately. The before-block's enum-plus-match is already half the pattern, with exhaustive matching forcing every method to handle every state, so adding a variant breaks each match until it's handled. The pattern tab goes further into the type-state pattern from the Rust book: each state is its own type, transitions consume the old value and return the new type, and PublishedDoc simply has no publish() method. Invalid transitions stop being runtime answers and become methods that don't exist, which is the strongest version of this pattern any mainstream language can express.

When the Switch Should Stay

The honest threshold question is growth. A machine with three stable states and one branching method gains nothing from five classes and an interface except distance between the reader and the logic, and the before-block's switch was clearer for that case. The pattern starts paying when the ladders multiply across methods, when states keep arriving, and when different people own different modes, the same ownership test that justified other patterns in this catalog.


The pattern also carries a documented coupling cost: because states appoint their successors, Draft names Moderation in its body, and inserting a state between two neighbors means editing the predecessor. The Rust book calls this out directly in its State chapter, and the standard mitigation is a transition table, the machine's arrows as data, when the topology churns faster than the behavior.

Best Practices

The pattern is a switch turned into classes, and these five keep the machine debuggable:


Three stable states branching in one method is a switch, and it should stay one. The pattern pays off when states multiply, every method carries its own copy of the ladder, and the copies start disagreeing.

Check Yourself

Two questions before you go. First: the second publish() call was refused without a single conditional on the Document class, so where does that decision live, and who decided not to transition? Second: State and Strategy share a class diagram, so what single question separates them, and what should you grep for to answer it in unfamiliar code? Both answers are on this page.


Then go spot it in the wild, starting with the machine underneath this page's delivery: TCP is specified as eleven states from LISTEN through ESTABLISHED to TIME-WAIT, progressing on events exactly like the document walked its workflow. Order pipelines, game character modes, and UI routers all run the same machinery. Last, find the field named status or mode in your own codebase and count the methods that branch on it. One branch is a field doing its job, and five matching ladders are a state machine asking for its classes.

the_mode_field.py

class Document:
    def __init__(self):
        self.state = "draft"

    def publish(self, is_admin: bool = False) -> str:
        # One ladder per method, and they all must agree
        if self.state == "draft":
            self.state = "moderation"
            return "sent to moderation"
        elif self.state == "moderation":
            if is_admin:
                self.state = "published"
                return "published"
            return "moderation requires an admin"
        return "already published"

    def render(self) -> str:
        # The same ladder again, and in every future method too
        if self.state == "draft":
            return "draft preview (author only)"
        elif self.state == "moderation":
            return "pending review banner"
        return "public page"

# Example usage
doc = Document()
print(doc.publish())              # sent to moderation
print(doc.publish(is_admin=True)) # published
print(doc.render())               # public page

state.py

from abc import ABC, abstractmethod

class State(ABC):  # State: the document's behavior in one mode
    @abstractmethod
    def publish(self, doc: "Document", is_admin: bool) -> str: ...

    @abstractmethod
    def render(self) -> str: ...

class Draft(State):  # ConcreteState: appoints its own successor
    def publish(self, doc: "Document", is_admin: bool) -> str:
        doc.set_state(Moderation())
        return "sent to moderation"

    def render(self) -> str:
        return "draft preview (author only)"

class Moderation(State):  # ConcreteState
    def publish(self, doc: "Document", is_admin: bool) -> str:
        if is_admin:
            doc.set_state(Published())
            return "published"
        return "moderation requires an admin"

    def render(self) -> str:
        return "pending review banner"

class Published(State):  # ConcreteState: a terminal mode
    def publish(self, doc: "Document", is_admin: bool) -> str:
        return "already published"

    def render(self) -> str:
        return "public page"

class Document:  # Context: a thin host that delegates to its mode
    def __init__(self):
        self._state: State = Draft()

    def set_state(self, state: State) -> None:
        self._state = state

    def publish(self, is_admin: bool = False) -> str:
        return self._state.publish(self, is_admin)

    def render(self) -> str:
        return self._state.render()

# Example usage
doc = Document()
print(doc.publish())               # sent to moderation
print(doc.publish())               # moderation requires an admin
print(doc.publish(is_admin=True))  # published
print(doc.publish())               # already published