State Pattern
What is it? (The Analogy)
Think about a vending machine. It behaves COMPLETELY differently depending on what state it's in:
- Idle state: It's waiting. If you press a button, it says "Please insert money first." If you insert money, it moves to the "Has Money" state.
- Has Money state: It's ready. If you press a button, it dispenses your item and goes to "Dispensing" state. If you insert more money, it just adds to the balance.
- Dispensing state: It's dropping your snack. You can't insert money or press buttons until it finishes. Then it goes back to "Idle."
- Out of Stock state: Nothing works. Insert money? It gives it back. Press a button? "Sorry, out of stock."
The vending machine is the same machine the whole time, but it acts differently depending on its current state. This is the State Pattern: an object changes its behavior when its internal state changes, as if it became a different object.
Key difference from Strategy: In Strategy, the CLIENT chooses the behavior. In State, the OBJECT ITSELF changes behavior automatically based on internal conditions.
Why do we need it?
Without the State Pattern, you get nightmarish if-else chains checking the current state in EVERY method:
1// BAD EXAMPLE -- State checked everywhere with if-else
2public class VendingMachine {
3 private String state = "IDLE";
4
5 public void insertMoney(int amount) {
6 if (state.equals("IDLE")) {
7 System.out.println("Money accepted!");
8 state = "HAS_MONEY";
9 } else if (state.equals("HAS_MONEY")) {
10 System.out.println("Added more money.");
11 } else if (state.equals("DISPENSING")) {
12 System.out.println("Please wait, dispensing...");
13 } else if (state.equals("OUT_OF_STOCK")) {
14 System.out.println("Sorry, returning your money. We're empty.");
15 }
16 }
17
18 public void pressButton() {
19 if (state.equals("IDLE")) {
20 System.out.println("Insert money first!");
21 } else if (state.equals("HAS_MONEY")) {
22 System.out.println("Dispensing...");
23 state = "DISPENSING";
24 } else if (state.equals("DISPENSING")) {
25 System.out.println("Already dispensing, please wait.");
26 } else if (state.equals("OUT_OF_STOCK")) {
27 System.out.println("Sorry, out of stock!");
28 }
29 }
30
31 // Every method repeats the SAME state checks!
32 // Adding a new state means modifying EVERY method!
33}Imagine adding an "MAINTENANCE" state. You'd need to add a new else if branch in EVERY method. Forget one? Bug.
The pain: State logic is scattered across all methods. Adding a new state means touching every method. Each method becomes an unreadable wall of if-else. It's impossible to see all behavior for one state in one place.
How it works -- Step by Step
- Identify the states -- List all the distinct states your object can be in (Idle, HasMoney, Dispensing, OutOfStock).
- Define a State interface -- Declare all the actions/events that can happen (insertMoney, pressButton, dispense, etc.).
- Create a concrete class for each state -- Each state class implements the interface, defining behavior for THAT specific state.
- The Context holds a reference to the current state -- The main object (VendingMachine) delegates all actions to its current state object.
- State transitions -- Each state class can change the context's state to trigger a transition. For example,
IdleStateswitches toHasMoneyStatewhen money is inserted.
Let's Build It Together
Let's build a Phone Call System -- a phone behaves differently when it's idle, ringing, on a call, or on hold.
1// ========================================
2// Step 1: Define the State interface
3// Every state must handle all possible actions
4// ========================================
5
6public interface PhoneState {
7 void pickUp(Phone phone);
8 void hangUp(Phone phone);
9 void dial(Phone phone, String number);
10 void holdButton(Phone phone);
11 String getStateName();
12}1// ========================================
2// Step 2: The Phone (Context) -- delegates everything to current state
3// ========================================
4
5public class Phone {
6 private PhoneState currentState;
7 private String currentNumber;
8
9 public Phone() {
10 // Start in idle state
11 this.currentState = new IdleState();
12 System.out.println("Phone is ready. State: " + currentState.getStateName());
13 }
14
15 // Delegate all actions to the current state
16 public void pickUp() {
17 currentState.pickUp(this);
18 }
19
20 public void hangUp() {
21 currentState.hangUp(this);
22 }
23
24 public void dial(String number) {
25 currentState.dial(this, number);
26 }
27
28 public void holdButton() {
29 currentState.holdButton(this);
30 }
31
32 // State transition -- called BY the state classes
33 public void setState(PhoneState newState) {
34 System.out.println(" >>> State changed: " + currentState.getStateName()
35 + " -> " + newState.getStateName());
36 this.currentState = newState;
37 }
38
39 public void setCurrentNumber(String number) { this.currentNumber = number; }
40 public String getCurrentNumber() { return currentNumber; }
41 public String getStateName() { return currentState.getStateName(); }
42}1// ========================================
2// Step 3: Implement each state -- ALL behavior for one state
3// is in ONE class (much easier to understand!)
4// ========================================
5
6// ---- IDLE STATE ----
7public class IdleState implements PhoneState {
8 @Override
9 public void pickUp(Phone phone) {
10 System.out.println("Phone picked up. Ready to dial!");
11 phone.setState(new DialingState());
12 }
13
14 @Override
15 public void hangUp(Phone phone) {
16 System.out.println("Phone is already idle. Nothing to hang up.");
17 }
18
19 @Override
20 public void dial(Phone phone, String number) {
21 System.out.println("Pick up the phone first before dialing!");
22 }
23
24 @Override
25 public void holdButton(Phone phone) {
26 System.out.println("Can't hold -- no active call.");
27 }
28
29 @Override
30 public String getStateName() { return "IDLE"; }
31}
32
33// ---- DIALING STATE ----
34public class DialingState implements PhoneState {
35 @Override
36 public void pickUp(Phone phone) {
37 System.out.println("Phone is already picked up.");
38 }
39
40 @Override
41 public void hangUp(Phone phone) {
42 System.out.println("Hanging up. Back to idle.");
43 phone.setCurrentNumber(null);
44 phone.setState(new IdleState());
45 }
46
47 @Override
48 public void dial(Phone phone, String number) {
49 System.out.println("Dialing " + number + "... ring ring ring...");
50 phone.setCurrentNumber(number);
51 phone.setState(new RingingState());
52 }
53
54 @Override
55 public void holdButton(Phone phone) {
56 System.out.println("Can't hold -- haven't connected yet!");
57 }
58
59 @Override
60 public String getStateName() { return "DIALING"; }
61}
62
63// ---- RINGING STATE ----
64public class RingingState implements PhoneState {
65 @Override
66 public void pickUp(Phone phone) {
67 System.out.println("Call connected with " + phone.getCurrentNumber() + "! Say hello!");
68 phone.setState(new OnCallState());
69 }
70
71 @Override
72 public void hangUp(Phone phone) {
73 System.out.println("Call cancelled before answer. Back to idle.");
74 phone.setCurrentNumber(null);
75 phone.setState(new IdleState());
76 }
77
78 @Override
79 public void dial(Phone phone, String number) {
80 System.out.println("Already ringing! Wait for answer or hang up.");
81 }
82
83 @Override
84 public void holdButton(Phone phone) {
85 System.out.println("Can't hold -- call hasn't connected yet.");
86 }
87
88 @Override
89 public String getStateName() { return "RINGING"; }
90}
91
92// ---- ON CALL STATE ----
93public class OnCallState implements PhoneState {
94 @Override
95 public void pickUp(Phone phone) {
96 System.out.println("Already on a call!");
97 }
98
99 @Override
100 public void hangUp(Phone phone) {
101 System.out.println("Call ended with " + phone.getCurrentNumber() + ". Goodbye!");
102 phone.setCurrentNumber(null);
103 phone.setState(new IdleState());
104 }
105
106 @Override
107 public void dial(Phone phone, String number) {
108 System.out.println("Can't dial during a call. Hang up first.");
109 }
110
111 @Override
112 public void holdButton(Phone phone) {
113 System.out.println("Call with " + phone.getCurrentNumber() + " is now on hold. Playing hold music...");
114 phone.setState(new OnHoldState());
115 }
116
117 @Override
118 public String getStateName() { return "ON_CALL"; }
119}
120
121// ---- ON HOLD STATE ----
122public class OnHoldState implements PhoneState {
123 @Override
124 public void pickUp(Phone phone) {
125 System.out.println("Resuming call with " + phone.getCurrentNumber() + "!");
126 phone.setState(new OnCallState());
127 }
128
129 @Override
130 public void hangUp(Phone phone) {
131 System.out.println("Ending held call with " + phone.getCurrentNumber() + ".");
132 phone.setCurrentNumber(null);
133 phone.setState(new IdleState());
134 }
135
136 @Override
137 public void dial(Phone phone, String number) {
138 System.out.println("Can't dial while a call is on hold.");
139 }
140
141 @Override
142 public void holdButton(Phone phone) {
143 System.out.println("Resuming call with " + phone.getCurrentNumber() + "!");
144 phone.setState(new OnCallState());
145 }
146
147 @Override
148 public String getStateName() { return "ON_HOLD"; }
149}1// ========================================
2// Step 4: Run it -- watch the phone change behavior!
3// ========================================
4
5public class PhoneApp {
6 public static void main(String[] args) {
7 Phone phone = new Phone();
8
9 System.out.println("\n--- Try dialing without picking up ---");
10 phone.dial("555-1234"); // Can't dial when idle!
11
12 System.out.println("\n--- Normal call flow ---");
13 phone.pickUp(); // Idle -> Dialing
14 phone.dial("555-1234"); // Dialing -> Ringing
15 phone.pickUp(); // Ringing -> OnCall
16
17 System.out.println("\n--- Put on hold and resume ---");
18 phone.holdButton(); // OnCall -> OnHold
19 phone.holdButton(); // OnHold -> OnCall (toggle!)
20
21 System.out.println("\n--- End the call ---");
22 phone.hangUp(); // OnCall -> Idle
23
24 System.out.println("\nFinal state: " + phone.getStateName());
25 }
26}Aha moment: Each state class is a clean, self-contained unit. ALL behavior for "On Hold" lives in OnHoldState. Want to add a "Conference Call" state? Just create a new class -- you never touch the existing state classes!
Visual Mental Model
1 +-------+ pickUp +---------+ dial +---------+
2 | IDLE | --------> | DIALING | ------> | RINGING |
3 +-------+ +---------+ +---------+
4 ^ | |
5 | hangUp | hangUp | pickUp
6 | v v
7 | +-------+ +---------+
8 +<--------------- | |<-------- | ON_CALL |
9 | hangUp | IDLE | hangUp +---------+
10 | +-------+ | ^
11 | hold | | hold
12 | v |
13 | +---------+
14 +<--------------------------------- | ON_HOLD |
15 hangUp +---------+
16
17 Each arrow = a state transition triggered by a user action.
18 Each box = a separate State class with its own behavior.Common Mistakes & Gotchas
- Confusing State with Strategy -- In State, transitions happen AUTOMATICALLY inside the state classes. In Strategy, the CLIENT explicitly chooses which algorithm to use. If the object changes its own behavior, use State. If the user picks the behavior, use Strategy.
- Putting transition logic in the context -- The Phone class should NOT decide which state to go to. Each state class decides the next state. This keeps the state machine logic localized.
- Creating too many state classes for simple cases -- If you only have 2 states with 2 actions, a simple boolean might be fine. Use the State Pattern when you have 3+ states with 3+ actions.
- Circular state creation -- States create new instances each transition. For long-running systems, consider caching state objects (flyweight) or using enum-based states.
- Forgetting "impossible" transitions -- Every state must handle every action, even if the response is "you can't do that right now." Don't leave methods empty -- at least log or throw an appropriate error.
Interview Tip
The State Pattern appears in many LLD interview problems: Vending Machine, ATM Machine, Elevator System, Traffic Light, Order Processing (placed -> paid -> shipped -> delivered). When you see a problem with distinct states and transitions, immediately say: "I'll model this using the State Pattern, with each state as a separate class." Draw the state diagram FIRST (the boxes and arrows), then translate it to classes. This structured approach impresses interviewers.
Quick Quiz
- You're designing a traffic light system with states: GREEN, YELLOW, RED, and FLASHING_RED (for emergencies). What actions/events would trigger state transitions? Sketch the state diagram.
- What would happen if the
OnCallState.holdButton()method forgot to callphone.setState(new OnHoldState())? What user experience would result?
- In our phone example, could we use an enum instead of separate classes for each state? What would we gain and what would we lose?
Summary -- Key Takeaways
- State Pattern lets an object change behavior based on its internal state, as if it were a different object. Each state is a separate class.
- Each state class handles all actions for that state, including deciding when and where to transition.
- The Context (Phone) delegates to the current state and never contains if-else state checks.
- Use it when your object has distinct states with different behavior and well-defined transitions between them (vending machines, order lifecycles, media players, etc.).