Skip to content
Sanfor2004
← All articles
Design PatternsDesign PatternsBehavioral PatternsPythonC++Software Engineering

State (Behavioral Pattern)

By Ahmed (Sanfor) · 11 Sept 2026 · 5 min read
A traffic signal changes between distinct operating states.

Start with the design patterns overview · Part 20 of 23 · Behavioral patterns

Change an object’s behavior when its internal state changes by delegating to state objects. This article follows the example in my 23 Design Patterns repository, connecting the problem, participating classes, Python and C++20 implementations, and the trade-offs that decide whether to use it.

The Problem

A door reacts to the same button differently when open and closed; richer devices add locked or jammed states.

Naive Solution

if (open) open = false;
else open = true; // becomes scattered as states and events grow

Why It Becomes a Problem

A boolean toggle is enough for two states, but copying state conditionals across many events makes transitions inconsistent.

The Idea

Door delegates press to its current DoorState, and that state selects the next state.

Reading the illustration

A traffic light responds differently to the same timer tick depending on whether it is currently red, yellow, or green.

In the repository example, the same design idea addresses this software problem: A door reacts to the same button differently when open and closed; richer devices add locked or jammed states.

Structure

Diagram · Python source · C++20 source

State sketch map: Door::press() leads through DoorState to Open ↔ Closed.
State: trace the example from caller through the pattern boundary to its collaborator or result. The arrows show flow, not ownership. Open the full-size diagram.
Door::press()  -->  DoorState  -->  Open ↔ Closed

Participants

Door is the context. DoorState defines press and name. Open and Closed hold non-owning links to the next state; main keeps both alive longer than Door.

Canonical roles in this example:

  • Context — The object that uses a Strategy or delegates behavior to its current State. Here: Door.
  • State interface — The contract through which a Context delegates state-dependent behavior. Here: DoorState.
  • Concrete State — An implementation defining behavior and transitions for one State. Here: Open, Closed.

Python Example

The complete Python source is shown first.

class Closed:
    name = "closed"

    def press(self, door):
        door.state = Open()


class Open:
    name = "open"

    def press(self, door):
        door.state = Closed()


class Door:
    def __init__(self):
        self.state = Closed()

    def press(self):
        self.state.press(self)


if __name__ == "__main__":
    door = Door()
    print(door.state.name)
    door.press()
    print(door.state.name)
    door.press()
    print(door.state.name)

Python Output

closed
open
closed

Code Walkthrough

Door is the context. DoorState defines press and name. Open and Closed hold non-owning links to the next state; main keeps both alive longer than Door.

Start at the final call in the Python example. Follow the middle role in the diagram and compare how the C++20 version handles the same responsibility.

C++20 Example

#include <iostream>
#include <string_view>

class Door;
struct DoorState {
    virtual ~DoorState() = default;
    virtual void press(Door& door) const = 0;
    virtual std::string_view name() const = 0;
};
class Door {
    const DoorState* state_;
public:
    explicit Door(const DoorState& state) : state_(&state) {}
    void change(const DoorState& state) { state_ = &state; }
    void press() { state_->press(*this); }
    std::string_view name() const { return state_->name(); }
};
struct Open final : DoorState {
    const DoorState* next = nullptr;
    void press(Door& door) const override { if (next) door.change(*next); }
    std::string_view name() const override { return "open"; }
};
struct Closed final : DoorState {
    const DoorState* next = nullptr;
    void press(Door& door) const override { if (next) door.change(*next); }
    std::string_view name() const override { return "closed"; }
};
int main() {
    Open open;
    Closed closed;
    open.next = &closed;
    closed.next = &open;
    Door door{closed};
    std::cout << door.name() << '\n';
    door.press();
    std::cout << door.name() << '\n';
    door.press();
    std::cout << door.name() << '\n';
}

C++20 Output

closed
open
closed

When to Use

Use it when state-dependent behavior and transitions spread across several operations.

Use cases

Protocol sessions and device workflows are suitable contexts when transition rules are explicit.

When NOT to Use

Avoid it for a trivial toggle or a small explicit enum transition table that stays readable.

Advantages

Behavior is grouped by state and transitions can be inspected locally.

Trade-offs

Classes and lifetime (The interval during which an object exists and may be used according to its rules) relationships add complexity. The demo keeps state objects outside Door, so transitions never destroy the currently executing state; larger designs must preserve that safety.

Strategy · Observer

Common Confusion

Strategy is usually selected by a client to choose an algorithm. State represents lifecycle (The modeled stages and transitions of a domain entity, distinct from a C++ object’s lifetime) and may choose its own transitions.

Terms to Remember

  • State — Let an object’s current state determine its response and transitions.
  • Context — The object that uses a Strategy or delegates behavior to its current State. Example: Door.
  • State interface — The contract through which a Context delegates state-dependent behavior. Example: DoorState.
  • Concrete State — An implementation defining behavior and transitions for one State. Example: Open, Closed.

Interview Vocabulary

  • state transition — A move from one modeled condition to another after an event.
  • runtime behavior — What the program does while executing, including behavior selected from runtime input.
  • delegation — An object asks a collaborator to perform part of its work.

Interview Question

Who decides the next state here, and how is that different from choosing a shipping Strategy?

Mini Challenge

Add Locked so press keeps it locked; provide a separate unlock event and test the transition sequence.

Compare the two versions

Python creates a new stateless State Object for each transition. C++ reuses Open and Closed Objects through borrowed pointers; they must outlive Door. Both move transition behavior into State Objects. A boolean toggle is simpler for this tiny domain. The two examples express the same pattern responsibility; compare their setup and output before changing an input.

Check yourself

  1. Who chooses the next State when the door button is pressed?
  2. When would the naive solution on this page be easier to maintain? Give a concrete example.
  3. Change one input in the Python example. Predict the output and explain which responsibility handles the change.

Run and explore the example

The Python code comes from python/main.py, with expected output. The C++20 code comes from behavioral/state/cpp/main.cpp. Follow the repository’s C++20 build instructions to compile it and compare the result with expected.txt. The output demonstrates this example’s behavior; it does not cover every input or the challenge above.

The source example and adapted explanation are © 2026 Sanfor2004, provided under the MIT license. The sketchbook cover is an illustration preserved from this site’s original pattern lessons.

Continue the series

Ahmed (Sanfor)Backend and automation engineer in Alexandria. I build the systems businesses run on, and paint between deploys.

Keep reading

Design PatternsAbstract Factory (Creational Pattern)5 minDesign PatternsAdapter (Structural Pattern)4 minDesign PatternsBridge (Structural Pattern)5 min

Have manual work eating your week?

Book a free call