State Pattern: The Fan That Changes Its Mood
Learn the State design pattern with a ceiling fan regulator story, simple TypeScript and C# code, state diagrams, real software examples, and practice.
The story of the ceiling fan regulator ๐
Come to a small house in Nagpur in the month of May. It is 44 degrees outside. Inside, fourteen-year-old Meera and her little brother Arjun are sitting under the ceiling fan, and the fan has a modern regulator with just one button.
Watch what that one button does:
- The fan is off. Meera presses the button. The fan starts turning slowly.
- She presses the same button again. The fan moves to medium speed.
- She presses it once more. Now the fan runs fast, and Arjun's homework papers fly all over the room.
- One long press, and the fan goes back to off.
Arjun, who is nine and full of questions, asks the question this whole post will answer: "Didi, it is the same button. Why does it do a different thing every time?"
Meera thinks for a second and says: "Because the fan remembers where it is. An off fan hears the press as start. A slow fan hears the same press as go faster."
Stop and think about this. It is the same button every time. Meera's finger does the same action every time. But the fan's reply is different each time. Why? Because the fan's answer depends on its current state. An "off" fan treats the press as "start slowly". A "slow" fan treats the same press as "go to medium". A "fast" fan treats it as "go back to slow".
The fan is like a person whose mood changes. Ask your mother for ice cream when she is happy, and the answer is yes. Ask the same question when she is busy cooking, and the answer is "later, beta". Same question, different answer, because the state changed.
This is exactly the State pattern. One object, one set of buttons, but the behaviour of each button depends on which state the object is in right now. Keep Meera, Arjun, and this fan in your mind. The whole post is their story, and we will build the fan fully in code very soon.
Here is their hot afternoon, drawn as a journey:
What is the State pattern? ๐ง
The State pattern is a behavioral design pattern. Behavioral patterns are about how objects behave and talk to each other.
Here is the plain definition. Sometimes an object must act differently depending on its internal condition. The State pattern says: do not keep one big class with a status variable and if-else checks everywhere. Instead, make one small class for each state. All these state classes follow one common interface. The main object โ called the context โ keeps a reference to one state object at a time and forwards every state-dependent call to it.
When the situation changes, the context simply swaps the state object for a different one. From outside, it looks like the object changed its class. Magic? No โ just a different helper object is now answering the calls.
This pattern comes from the famous 1994 book by the Gang of Four (GoF), where it is also called Objects for States. It is the clean, object-oriented way to build a finite state machine โ a fancy name for "a thing with a fixed set of states and fixed rules for moving between them".
One-line summary: State pattern = give each mood of an object its own class, and let the object forward its work to whichever mood is currently active.
Remember these two roles:
- Context โ the main object that clients talk to. In our story, the fan.
- State โ the common interface, plus one concrete class per condition. In our story, Off, Slow, Medium, and Fast.
There is one more special thing. State objects usually know which state comes next. The Slow state knows that one more press means Medium. So the state itself tells the context, "replace me with Medium now". The transition rules live right beside the behaviour. This is the heart of the pattern.
College corner: what you are really building here is a deterministic finite state machine (FSM) from your theory-of-computation course, dressed in object-oriented clothes. The states are the FSM's states, the button events are the input alphabet, and each setState call is a transition function entry. If the output depends on the transition (print "speeding up" while moving), you have a Mealy machine; if the output depends only on the state itself (the wattage drawn in each speed), that part behaves like a Moore machine. Real devices, protocols, and parsers mix both flavours, and the State pattern hosts either comfortably.
The problem it solves ๐ต
Let us first write the fan without the pattern, the way most of us write it the first time. Meera takes her school laptop and keeps a string variable, branching on it everywhere:
// BAD CODE: one class, one status variable, if-else everywhere
class CeilingFan {
private status: string = "off"; // "off" | "slow" | "medium" | "fast"
pressButton(): void {
if (this.status === "off") {
console.log("Whirr... starting slowly");
this.status = "slow";
} else if (this.status === "slow") {
console.log("Speeding up to medium");
this.status = "medium";
} else if (this.status === "medium") {
console.log("Full speed! Hold your papers!");
this.status = "fast";
} else if (this.status === "fast") {
console.log("Back to slow");
this.status = "slow";
}
}
longPress(): void {
if (this.status === "off") {
console.log("Already off");
} else {
console.log("Switching off");
this.status = "off";
}
}
showPowerUse(): void {
if (this.status === "off") console.log("0 watts");
else if (this.status === "slow") console.log("30 watts");
else if (this.status === "medium") console.log("55 watts");
else if (this.status === "fast") console.log("75 watts");
}
}It works today. Arjun is impressed. But see the troubles hiding inside:
- The same ladder repeats in every method.
pressButton,longPress, andshowPowerUseall branch on the samestatus. A real fan class will also haveremoteSignal(),timerTick(),display()โ and each will grow its own copy of the ladder. - Adding a new state means editing everything. Suppose the company adds a Sleep mode that slowly reduces speed at night. You must open every method, find every ladder, and add one more branch. Miss one ladder, and you have a bug that appears only at midnight.
- The transition rules are scattered. Which state follows which? The answer is spread across many methods. Nobody can read the "rule book" of the fan in one place.
- Typos hide quietly. Write
"meduim"by mistake and the compiler says nothing. The fan just stops responding one day.
Watch how fast this rots. Each new state adds a branch to every ladder, and each new method adds a whole new ladder. The if-else line count grows like a multiplication table, while the State pattern version grows by a fixed small amount per state:
Before fixing the code, let us first write the rule book that the if-else version hides. Every state machine can be written as a simple table โ one row per state, one column per event:
| Current state | Short press goes to | Long press goes to | Power use |
|---|---|---|---|
| Off | Slow | Off (stays put) | 0 watts |
| Slow | Medium | Off | 30 watts |
| Medium | Fast | Off | 55 watts |
| Fast | Slow | Off | 75 watts |
| Sleep (new!) | Slow | Off | 25 watts |
This table is the truth of the fan. And the best picture of this truth is a state diagram. Here is the full machine, including the new night-time Sleep mode the company wants, with the three running speeds grouped inside one "Running" super-state:
The diagram is clean. The if-else code is not. The State pattern makes the code look like the diagram. That is its whole job.
How it works, step by step ๐ ๏ธ
Here is the recipe Meera follows to refactor her fan. Follow it slowly:
- Find the context. Which class behaves differently based on a status field? That is your context. For us,
CeilingFan. - List the state-dependent actions. Which methods change behaviour with the status? For us,
pressButtonandlongPress. These become the methods of the State interface. - Write one class per state.
OffState,SlowState,MediumState,FastState. Move each branch of the old if-else ladder into the matching class. - Give states a way to reach the context. Pass the fan into each state method. The state needs it for two jobs: to read fan data, and to ask for a transition.
- Replace the status string with a state object. The context now holds
private state: FanStateand offers asetState()method. - Delete the ladders. Each public method of the fan becomes one line: forward the call to the current state.
- Wire the transitions. Inside each state, when it is time to move on, call
fan.setState(new NextState()).
After the refactor, here is what one button press actually does inside the program. Notice that there is no question being asked anywhere โ the answer comes from which object is installed:
And here is the class structure that makes it possible:
Notice the most beautiful part: there is no if-else left. The "branching" happens automatically, because whichever state object is installed answers the call. Choosing the object is the branch.
Real-life code example ๐ป
Now let us build Meera's fan completely in TypeScript. Read the comments โ they tell the story.
// ---------- The State interface ----------
// Every state must know how to answer both buttons.
interface FanState {
readonly name: string;
press(fan: CeilingFan): void; // short press of the button
longPress(fan: CeilingFan): void; // long press = switch off
}
// ---------- The Context ----------
// The fan does NOT know speed logic. It only forwards calls.
class CeilingFan {
private state: FanState;
constructor() {
this.state = new OffState(); // every fan starts off
}
setState(next: FanState): void {
console.log(` [fan] state: ${this.state.name} -> ${next.name}`);
this.state = next;
}
// public buttons โ each is ONE line, no if-else
pressButton(): void {
this.state.press(this);
}
longPressButton(): void {
this.state.longPress(this);
}
}
// ---------- Concrete states ----------
class OffState implements FanState {
readonly name = "OFF";
press(fan: CeilingFan): void {
console.log("Whirr... blades start turning slowly");
fan.setState(new SlowState()); // Off -> Slow
}
longPress(fan: CeilingFan): void {
console.log("Fan is already off. Nothing to do.");
}
}
class SlowState implements FanState {
readonly name = "SLOW";
press(fan: CeilingFan): void {
console.log("Speeding up to medium");
fan.setState(new MediumState()); // Slow -> Medium
}
longPress(fan: CeilingFan): void {
console.log("Switching off");
fan.setState(new OffState()); // Slow -> Off
}
}
class MediumState implements FanState {
readonly name = "MEDIUM";
press(fan: CeilingFan): void {
console.log("Full speed! Hold your homework papers!");
fan.setState(new FastState()); // Medium -> Fast
}
longPress(fan: CeilingFan): void {
console.log("Switching off");
fan.setState(new OffState()); // Medium -> Off
}
}
class FastState implements FanState {
readonly name = "FAST";
press(fan: CeilingFan): void {
console.log("Cycling back to slow");
fan.setState(new SlowState()); // Fast -> Slow
}
longPress(fan: CeilingFan): void {
console.log("Switching off");
fan.setState(new OffState()); // Fast -> Off
}
}
// ---------- Meera uses the fan ----------
const fan = new CeilingFan();
fan.pressButton(); // Off -> Slow
fan.pressButton(); // Slow -> Medium
fan.pressButton(); // Medium -> Fast
fan.longPressButton(); // Fast -> Off
fan.longPressButton(); // still Off โ safe, no crashThe output:
Whirr... blades start turning slowly
[fan] state: OFF -> SLOW
Speeding up to medium
[fan] state: SLOW -> MEDIUM
Full speed! Hold your homework papers!
[fan] state: MEDIUM -> FAST
Switching off
[fan] state: FAST -> OFF
Fan is already off. Nothing to do.Read the client code at the bottom again. Meera presses the same method pressButton() and gets different behaviour every time, and the fan walked across three different states and back. Not a single if decided this. The deciding was done by which object was installed in fan.state.
Here is that conversation between Meera, the fan, and the state objects, drawn as a sequence:
Want the Sleep mode from Figure 3 now? Write one new class SleepState, decide its transitions, and make FastState point to it at night. No other old class is touched. That is the Open/Closed Principle working for you: open for extension, closed for modification.
The same idea in C# ๐ฆ
Meera's cousin Vikram, a second-year CSE student in Hyderabad, sees her code and says, "We did the same thing in our OOP lab โ with a traffic signal." Here is his version: three states โ Red, Green, Yellow โ and a timer that says "next!".
// The state interface
interface ISignalState
{
void Next(TrafficSignal signal); // timer fired, move ahead
}
// The context
class TrafficSignal
{
private ISignalState _state = new RedState();
public void SetState(ISignalState next) => _state = next;
public void TimerTick() => _state.Next(this);
}
class RedState : ISignalState
{
public void Next(TrafficSignal signal)
{
Console.WriteLine("RED -> GREEN: vehicles, please go");
signal.SetState(new GreenState());
}
}
class GreenState : ISignalState
{
public void Next(TrafficSignal signal)
{
Console.WriteLine("GREEN -> YELLOW: slow down");
signal.SetState(new YellowState());
}
}
class YellowState : ISignalState
{
public void Next(TrafficSignal signal)
{
Console.WriteLine("YELLOW -> RED: please stop");
signal.SetState(new RedState());
}
}
// Usage
var signal = new TrafficSignal();
signal.TimerTick(); // RED -> GREEN
signal.TimerTick(); // GREEN -> YELLOW
signal.TimerTick(); // YELLOW -> REDSame shape, different story. The signal forwards TimerTick() to its current state, and each state installs the next one.
College corner: notice one small extra trick used in real projects. When state classes hold no data of their own (like these), creating a fresh object on every transition is wasteful. You can create one shared instance per state and reuse it forever โ signal.SetState(GreenState.Instance). This pairs the State pattern with Singleton (one instance per state class) or Flyweight (shared stateless objects). The GoF book itself points this out: states that carry no instance data are perfect flyweights.
One more flavour: a music player in Python ๐ต
The same pattern, third language, so you see the idea is bigger than any syntax. Arjun's favourite example: the play button on a music app means "play" when stopped, "pause" when playing, and "resume" when paused.
class PlayerState:
"""Common interface: every state answers the play button."""
def press_play(self, player):
raise NotImplementedError
class Stopped(PlayerState):
def press_play(self, player):
print("Starting the song from the beginning")
player.state = Playing()
class Playing(PlayerState):
def press_play(self, player):
print("Pausing the song")
player.state = Paused()
class Paused(PlayerState):
def press_play(self, player):
print("Resuming from the same spot")
player.state = Playing()
class MusicPlayer: # the context
def __init__(self):
self.state = Stopped()
def play_button(self): # one line, no if-else
self.state.press_play(self)
p = MusicPlayer()
p.play_button() # Starting the song from the beginning
p.play_button() # Pausing the song
p.play_button() # Resuming from the same spotAnd its state diagram โ small, but exactly the same species as the fan:
Where you see it in real software ๐
The State pattern is everywhere once you learn to spot it:
- Order status in e-commerce. On Flipkart or Amazon, an order moves through Placed โ Packed โ Shipped โ Out for Delivery โ Delivered. The "Cancel" button behaves differently in each state: free cancel when Placed, maybe blocked when Out for Delivery. That is a state machine, and clean implementations give each status its own class.
- TCP network connections. The original GoF book itself used a
TCPConnectionexample. A connection can be Listening, Established, or Closed, and the sameopen()/close()/send()calls behave differently in each. This example is so classic that articles still refactor it today. - Game characters. A hero in a game can be Standing, Jumping, Ducking, or Dashing. Pressing the same jump button must behave differently in each pose. The free book Game Programming Patterns has a famous chapter showing how if-else character code becomes clean state classes.
- Media players. Play/Pause is one button with different meanings in Stopped, Playing, and Paused states โ exactly our Python example, and exactly our fan.
- Vending machines and ATMs. Idle, CoinInserted, Dispensing; or CardInserted, PinEntered, CashDispensed. Classic textbook and interview examples, because the hardware world is full of state machines.
- Document workflows. A blog post or news article moves Draft โ In Review โ Published โ Archived, and
publish()means something different at each step. - Open-source code to read. The iluwatar/java-design-patterns repository has a tidy Java example, and faif/python-patterns shows a Pythonic version. Libraries like XState (JavaScript) and Stateless (C#) are whole tools built around state machines.
If we peek into the places where working teams actually reach for explicit state machines, the spread looks roughly like this:
When to use it and when not to ๐ค
| Situation | Use State? |
|---|---|
| An object has 3+ states and many methods branch on the same status field | โ Yes โ the pattern removes all those ladders |
| The set of states keeps growing (new modes, new statuses) | โ Yes โ add a class, do not edit old code |
| Transition rules are getting confusing and scattered | โ Yes โ each state holds its own "what comes next" |
| You are clearly modelling a state machine (workflow, device, protocol) | โ Yes โ the code will match the diagram |
| Only two simple states that will never change | โ No โ one if is clearer than four classes |
| Behaviour does not really depend on a status at all | โ No โ you are forcing the pattern |
| The per-state behaviour is one tiny line each | โ Probably not โ a lookup table may be enough |
| You need to choose between independent algorithms picked by the user | โ No โ that is the Strategy pattern, not State |
A picture helps when you are stuck between State, Strategy, and plain if-else. Ask two questions: who triggers the behaviour change (the caller from outside, or the object itself from inside), and does the behaviour even change at runtime:
The fan sits deep in State territory: the behaviour changes while the program runs, and the object itself (not Meera) decides what the next state is. A payment choice sits in Strategy territory: the behaviour changes at runtime, but the customer picks it, and the options never push each other around.
Common mistakes students make ๐ง
The number one mistake: keeping the old if-else ladders even after creating state classes. Some students create OffState and SlowState, but inside the fan they still write if (state instanceof OffState). This is the worst of both worlds โ more classes and still ladders. After applying the pattern, the context should only forward calls, never inspect the state's type.
More traps to avoid:
- Forgetting to give states access to the context. A state must be able to call
fan.setState(...). If you forget to pass the fan into the state methods, your states can behave but never transition, and you end up putting transition logic back in the context's if-else. - Letting every state know every other state. If all six states create all other five, transitions become spaghetti. Keep each state knowing only its real neighbours. For very tangled machines, consider a central transition table instead.
- Putting shared data inside states. The fan's data (room name, power rating) belongs in the context. States should mostly hold behaviour. If a state holds data, that data is lost on every transition.
- Creating new state objects in a hot loop without need. If states are stateless, share one instance of each (the Flyweight/Singleton trick from the C# section) instead of
newon every press. - Using State when Strategy was needed. If the user freely picks the behaviour from a menu and the behaviours never switch each other, you wanted Strategy. Read the next section carefully โ this confusion appears in many exams and interviews.
A quick smell test for your own code: count how many methods branch on the same status field. Zero or one โ leave it alone. Three or more โ the State pattern will probably pay for itself.
Compare with cousins ๐ฏ
The State pattern has a famous twin and a few relatives. This table is exam gold:
| Pattern | Skeleton | Who switches the behaviour? | Do the variants know each other? | Models |
|---|---|---|---|---|
| State | Context delegates to an interface | The states themselves call setState | Yes โ Slow knows Medium comes next | A state machine (a connected graph) |
| Strategy | Context delegates to an interface (identical!) | The client picks and installs one | No โ strategies are strangers | A menu of interchangeable algorithms |
| Command | Action wrapped as an object | Caller queues/undoes commands | No | A request to run later |
| Observer | Subject notifies listeners | Events fire to all observers | No | One-to-many notifications |
The State vs Strategy row deserves a highlight, because their class diagrams are pixel-for-pixel the same: a context, an interface, several concrete classes. The difference is pure intent:
- Strategy is like choosing how to pay at a shop โ cash, UPI, or card. You (the client) pick one. Cash never says "now switch to UPI". The options do not know each other.
- State is like our fan โ Slow itself decides that the next press means Medium. The states form a chain and push the context along it.
College corner: this intent difference is the single most asked design-patterns interview question, so here is the blunt test in one line: if a variant ever calls context.setState(someOtherVariant), it is State. If only the client swaps the variant, it is Strategy. A second tell: State variants form a graph (you can draw arrows between them, like Figure 3), while Strategy variants form a flat list (no arrows possible โ UPI has no opinion about cash). When an interviewer shows you a class diagram and asks "State or Strategy?", the honest answer is: "Cannot say from the diagram alone โ show me who triggers the transitions."
Two more relations worth one sentence each: Bridge uses the same delegation shape but to let two class hierarchies grow independently, not to model states; and Flyweight/Singleton often partner with State to share stateless state objects across many contexts.
The whole pattern on one page ๐บ๏ธ
Before the revision box, here is the entire post as one mind map. If you can redraw this from memory, you own the pattern:
Quick revision box ๐ฆ
+--------------------------------------------------------------+
| STATE PATTERN โ REVISION |
+--------------------------------------------------------------+
| WHAT : Object changes behaviour when its inner state |
| changes; each state = its own class |
| ACTORS : Context (fan) + State interface + concrete states |
| KEY MOVE : Context forwards calls to current state object; |
| states call context.setState(next) themselves |
| KILLS : Repeated if-else ladders on one status field |
| REMEMBER : States know their neighbours (graph); |
| strategies do not (menu) |
| AVOID IF : Only 2 simple states that never change |
| EXAMPLES : Order status, TCP connection, game character, |
| media player, traffic signal, ceiling fan |
+--------------------------------------------------------------+Practice exercise โ๏ธ
Try these on your own. Write real code โ reading is not enough.
- Mobile phone ring modes. Build a
Phonecontext with three states: Ring, Vibrate, and Silent. The volume-down button moves Ring โ Vibrate โ Silent and stops there; the volume-up button moves the other way. An incoming call behaves differently in each state ("Ringing loudly!", "Bzzz bzzz", "Screen lights up quietly"). Print every transition, and first draw the state diagram on paper the way Figure 3 is drawn. - Add Sleep mode to the fan. Take the
CeilingFancode from this post and add aSleepState: a short press from Fast at night goes to Sleep, where the fan runs slow and a long press still switches off. How many existing classes did you have to edit? (Goal: only the one state that now points to Sleep.) - Spot the machine (thinking task). Pick any app on your phone โ a music player, a food delivery app, a game. Write down its states and draw the transition diagram on paper using arrows, like Figure 3. Then mark which buttons mean different things in different states. You will be surprised how many state machines you use before breakfast.
- College stretch. Replace the
new NextState()calls in the fan with a central transition table โ a map from (state, event) to next state โ and make all state classes shared singletons. Compare both designs: which one is easier to read, and which one is easier to print as documentation?
Frequently asked questions
- What is the State pattern in one line?
- The State pattern lets an object change its behaviour when its inner condition changes. Each condition becomes its own small class, and the main object simply forwards work to whichever state class is currently active.
- How is State different from a simple status variable with if-else?
- A status variable forces you to write if-else ladders in every method that cares about the status. The State pattern removes those ladders. Each state class holds its own behaviour, so adding a new state means adding one new class, not editing many old methods.
- Is the State pattern the same as Strategy?
- They look identical in structure, but the intent is different. Strategy objects are independent choices picked by the client, like cash or UPI. State objects know about each other and switch the context themselves, like a fan moving from slow to medium.
- When is the State pattern too much work?
- If you have only two states and the rules almost never change, a small if-else is simpler and clearer. Use the pattern when states keep growing, when many methods branch on the same status, or when transitions are getting hard to track.
- Who changes the state, the context or the state classes?
- Usually the state classes themselves. Each state knows which state should come next, so it asks the context to install the next state. This keeps the transition rule right next to the behaviour that causes it.
Further reading
Related Lessons
Strategy Pattern: Cycle, Bus, or Auto โ You Choose
Learn the Strategy design pattern with a simple school travel story, easy TypeScript and C# code, runtime swapping, real examples, and practice tasks.
Template Method Pattern: Chai and Coffee, Same Steps
Learn the Template Method design pattern with a chai and coffee story, easy TypeScript and C# code, hooks, diagrams, real examples, and practice tasks.
Visitor Pattern: The Doctor Who Visits Every Class
Learn the Visitor design pattern with a school health check-up story, double dispatch made simple, TypeScript and C# code, real examples, and practice.
Command Pattern: Turn Every Action into an Order Slip
Learn the Command pattern through a restaurant order slip story. Simple TypeScript and C# code with undo and redo, diagrams, tables, and practice tasks.