LLD
Machine Coding
Interview Course
Java โ€ข Interview Prep
Lesson 4 of 6
Prerequisites

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:

java
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

  1. Identify the states -- List all the distinct states your object can be in (Idle, HasMoney, Dispensing, OutOfStock).
  1. Define a State interface -- Declare all the actions/events that can happen (insertMoney, pressButton, dispense, etc.).
  1. Create a concrete class for each state -- Each state class implements the interface, defining behavior for THAT specific state.
  1. The Context holds a reference to the current state -- The main object (VendingMachine) delegates all actions to its current state object.
  1. State transitions -- Each state class can change the context's state to trigger a transition. For example, IdleState switches to HasMoneyState when 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.

java
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}
java
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}
java
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}
java
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

java
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

  1. 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.
  1. What would happen if the OnCallState.holdButton() method forgot to call phone.setState(new OnHoldState())? What user experience would result?
  1. 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.).