DEV Community

Cover image for State: The Traffic Light Pattern
Vignesh Athiappan
Vignesh Athiappan

Posted on

State: The Traffic Light Pattern

A traffic light is always in exactly one state, and its behavior depends entirely on which one:

  • Red — cars stop. After a timer, it becomes Green.
  • Green — cars go. After a timer, it becomes Yellow.
  • Yellow — cars slow. After a timer, it becomes Red.

Same object, but what it does — and what it becomes next — changes completely based on its current state. And crucially, the light switches itself. Red decides when to become Green. Nobody from outside picks the next state.

That's the State pattern: an object changes its own behavior when its internal state changes — as if it became a different class.

The problem it solves

Without it, you get a mess of flags and ifs scattered across every method:

public void Play()
{
    if (_state == "playing") Console.WriteLine("Already playing");
    else if (_state == "paused")  { _state = "playing"; Console.WriteLine("Resumed"); }
    else if (_state == "stopped") { _state = "playing"; Console.WriteLine("Starting"); }
}

public void Pause()
{
    if (_state == "playing") { ... }
    else if (_state == "paused") { ... }
    // the same tangle repeated in every method
}
Enter fullscreen mode Exit fullscreen mode

Every method re-checks the state. Add a new state and you edit every method. This is the classic "state machine as spaghetti."

The State way

Each state becomes its own class that knows two things: how to behave, and which state to move to next.

The state interface:

public interface IPlayerState
{
    void Play(MediaPlayer player);
    void Pause(MediaPlayer player);
}
Enter fullscreen mode Exit fullscreen mode

The context holds the current state and delegates to it:

public class MediaPlayer
{
    public IPlayerState State { get; set; }
    public MediaPlayer() => State = new StoppedState();   // start here

    public void Play()  => State.Play(this);    // delegate to current state
    public void Pause() => State.Pause(this);
}
Enter fullscreen mode Exit fullscreen mode

Each state is a class — and it decides the next state:

public class StoppedState : IPlayerState
{
    public void Play(MediaPlayer player)
    {
        Console.WriteLine("Starting playback");
        player.State = new PlayingState();   // the state switches itself
    }
    public void Pause(MediaPlayer player) => Console.WriteLine("Can't pause — already stopped");
}

public class PlayingState : IPlayerState
{
    public void Play(MediaPlayer player) => Console.WriteLine("Already playing");
    public void Pause(MediaPlayer player)
    {
        Console.WriteLine("Pausing");
        player.State = new PausedState();
    }
}

public class PausedState : IPlayerState
{
    public void Play(MediaPlayer player)
    {
        Console.WriteLine("Resuming");
        player.State = new PlayingState();
    }
    public void Pause(MediaPlayer player) => Console.WriteLine("Already paused");
}
Enter fullscreen mode Exit fullscreen mode

Watch it flow:

var player = new MediaPlayer();   // Stopped
player.Play();    // "Starting playback"  → Playing
player.Pause();   // "Pausing"            → Paused
player.Play();    // "Resuming"           → Playing
player.Play();    // "Already playing"
Enter fullscreen mode Exit fullscreen mode

No if (_state == ...) anywhere. Each state class handles its own behavior and points to the next. Adding a new state — say a buffering state — means writing one new class; no existing method gets touched.

Strategy vs State — the confusion, settled

The code looks almost identical. Both plug in an object and delegate to it. The difference is who decides, and whether it changes itself:

  • Who picks the plugged-in object? Strategy: you, from outside. State: the object switches itself.
  • Does it change during use? Strategy: no — pick once, it runs. State: yes — it flips as things happen.
  • Do the objects know each other? Strategy: no, they're independent algorithms. State: yes, each state knows the next.
  • Analogy. Strategy is Google Maps — you pick car vs walking. State is a traffic light — red becomes green on its own.

The tell: in Strategy, the context never reassigns its own strategy — you do, from outside. In State, PlayingState sets player.State = new PausedState() — the states rewrite the context from inside. Self-transitioning means State. Externally-chosen means Strategy.

Where you've seen it

  • Order lifecycle — Pending, Paid, Shipped, Delivered, each stage allowing different actions.
  • Workflow and approval engines — Draft, Submitted, Approved, Published.
  • Game character states — Idle, Running, Jumping, Attacking.
  • Connection states — Disconnected, Connecting, Connected, Reconnecting.
  • UI wizards — each step enables or disables different buttons.

One line to remember

State = traffic light. The object holds one state at a time, behaves according to it, and switches itself to the next — no giant if-else tangle.


Part 13 of a series on must-know design patterns in C#, explained the simple way. Earlier parts covered Singleton through Iterator. Next up: Template Method — defining a skeleton and letting subclasses fill in the steps.

Top comments (0)