"Design an elevator system."
Think about what an elevator can actually be doing at any moment: sitting idle, moving up, moving down, or standing with its doors open. It can only be in exactly one of these at a time — and what it does next depends entirely on which one it's currently in. That single observation is the whole design.
Clarifying questions
- How many elevators, and how many floors?
- Do we need to distinguish hall requests (button on a floor) from cabin requests (button inside the elevator)?
- Should we optimise scheduling, or is simple FIFO acceptable?
Why this is State, not Strategy
Recall the preview from Lesson 5: Strategy is chosen by a client from outside; State is an object's own internal condition, which the object itself moves between in response to events. No caller "picks" whether the elevator is idle or moving — the elevator becomes idle, then starts moving, then opens its doors, entirely on its own as events happen to it. That's the signal for State.
The state diagram
(MOVING_DOWN mirrors MOVING_UP and is omitted from the diagram for space — same transitions, opposite direction.)
Putting it together
class ElevatorState:
def request(self, elevator, floor): raise NotImplementedError
def arrived(self, elevator): raise NotImplementedError
def door_closed(self, elevator): raise NotImplementedError
class Idle(ElevatorState):
def request(self, elevator, floor):
elevator.target_floor = floor
if floor > elevator.current_floor:
elevator.set_state(MovingUp())
elif floor < elevator.current_floor:
elevator.set_state(MovingDown())
else:
elevator.set_state(DoorOpen())
class MovingUp(ElevatorState):
def arrived(self, elevator):
elevator.set_state(DoorOpen())
class MovingDown(ElevatorState):
def arrived(self, elevator):
elevator.set_state(DoorOpen())
class DoorOpen(ElevatorState):
def door_closed(self, elevator):
elevator.set_state(Idle())
class Elevator:
def __init__(self, elevator_id):
self.elevator_id = elevator_id
self.current_floor = 0
self.target_floor = None
self.state: ElevatorState = Idle()
def set_state(self, state: ElevatorState):
self.state = state
def request_floor(self, floor):
self.state.request(self, floor) # delegates — Elevator never
# needs an if/else on its own modeNotice Elevator.request_floor() never has an if/else on "am I idle or moving?" — it just delegates to whatever state it's currently in, and that state object decides what happens next and which state comes after. This is the direct code form of the state diagram above.
Scheduling: which floor next?
With several pending requests, naive FIFO can waste enormous effort — imagine 3 requests arriving as up-then-down-then-up: FIFO order sends the elevator up, back down, then up again, when serving them in a smarter order avoids the wasted trip entirely.
A common, simple improvement is SCAN (the "look" algorithm): keep moving in the current direction, picking up every request along the way, and only reverse once there are no more requests ahead in that direction. This mirrors how a lift disk controller schedules disk-head movement — same underlying idea, different domain.
Multiple elevators: a Controller
With more than one elevator, a new question appears that no single Elevator can answer alone: which elevator should answer this new hall request? That's a separate responsibility — dispatch — which belongs in its own class:
class ElevatorController:
def __init__(self, elevators):
self.elevators = elevators
def dispatch(self, floor, direction):
# simple first pass: nearest idle elevator, or one already
# heading the right way — a real system tunes this further
best = min(
self.elevators,
key=lambda e: abs(e.current_floor - floor)
)
best.request_floor(floor)What if the interviewer changes this?
Maintenance state that rejects request() calls — Elevator's core structure doesn't change, only the state graph grows one node.ElevatorController.dispatch() picks or reorders requests — the per-elevator state machine is untouched.Common mistakes
- Modeling elevator mode as scattered booleans/strings instead of an explicit State object per condition.
- Confusing State (the elevator's own condition) with Strategy (a client-chosen algorithm) — they look similar in code shape but answer different questions.
- Ignoring scheduling efficiency entirely and defaulting to plain FIFO without discussing the trade-off.
- Mixing fleet-wide dispatch logic into the `Elevator` class instead of a separate `ElevatorController`.
Quick recap
| Class / idea | Job |
|---|---|
| ElevatorState (Idle, MovingUp, MovingDown, DoorOpen) | Owns what happens next, per current condition. |
| Elevator | Holds current state, delegates every request to it. |
| Scheduling (SCAN/look) | Serve requests along the current direction before reversing. |
| ElevatorController | Decides which elevator answers a new hall request (fleet-wide dispatch). |
One thing to remember
When an object's own condition — not a client's choice — decides what happens next, model each condition as its own State, and let the object delegate to whichever one it currently holds.
Practice Zone
Four MCQs, then two reasoning questions.
Why is an elevator's idle/moving-up/moving-down/door-open behaviour modeled as State rather than Strategy?
Asked in


Why can't an elevator simply serve requests in the exact order they arrive (a plain FIFO queue)?
Asked in

With multiple elevators in one building, what does an ElevatorController need to decide that a single Elevator class doesn't?
Asked in


What are the two distinct kinds of requests an elevator system typically needs to model?
Asked in

Think it through, then reveal:
A first attempt models the elevator with a single boolean is_moving and a string direction. As more behaviour is added (door open/close, maintenance mode), this quickly grows into a tangle of boolean/string checks scattered everywhere. How does modeling it as an explicit State fix this?
Asked in

For a building with 4 elevators, what's a reasonable first-pass rule for choosing which elevator answers a new hall request, and why might it not be perfectly optimal?
Asked in


FAQ
Is the State pattern one of the five patterns this course focuses on?
This course's five dedicated pattern lessons are Singleton, Factory, Strategy, Observer and Adapter. State is a closely related, very common LLD idea — introduced here because the Elevator (and the next lesson, Vending Machine) are the clearest possible examples of it — but it doesn't get its own full lesson the way the other five do.
Why not just use a big switch/if-else on a string 'mode' field instead of separate State classes?
You could for a very small state machine — but as more states and transitions are added, that switch statement grows the same way the payment if/else did in Lesson 5, and invalid combinations aren't prevented by the type system the way they are when each state is its own class.
Does every elevator need its own ElevatorController?
No — typically one `ElevatorController` coordinates a whole bank of elevators for a building (or a floor's bank), which is itself a reasonable Singleton candidate if there's genuinely one coordinator per building.
Next, a machine that behaves completely differently depending on whether it already has your money — the clearest state-machine example in this course — Lesson 13: Design a Vending Machine →


