Courses › Low-Level Design › Problem Walkthroughs
Design an Elevator System
States, request dispatching and pluggable scheduling strategies.
Design the control system for a bank of elevators. This question is excellent for showing state modeling, a pluggable scheduling strategy, and clear separation between the controller and the elevators.
Requirements
- A building has N elevators and M floors
- Hall calls: a person on a floor presses Up or Down
- Car calls: a person inside an elevator presses a destination floor
- The system dispatches an elevator to each hall call and moves elevators efficiently
- Elevators have a capacity and doors that open/close
Out of scope: emergency modes, VIP/express elevators, energy optimization (good extensions).
Entities
Elevator behavior: the LOOK algorithm
Each elevator keeps a set of stops. It keeps moving in its current direction while there are stops ahead, serving them in order; when none remain ahead, it reverses if stops remain behind, otherwise goes idle. This is the LOOK algorithm — like a disk scheduler — and it avoids the starvation and thrashing of "always go to the nearest request".
from enum import Enum
class Direction(Enum):
UP = 1
DOWN = -1
IDLE = 0
class Elevator:
def __init__(self, elevator_id: int, floor: int = 0):
self.id = elevator_id
self.floor = floor
self.direction = Direction.IDLE
self.stops: set[int] = set()
def add_stop(self, floor: int):
if floor != self.floor:
self.stops.add(floor)
if self.direction is Direction.IDLE and self.stops:
self.direction = Direction.UP if floor > self.floor else Direction.DOWN
def _stops_ahead(self):
if self.direction is Direction.UP:
return any(f > self.floor for f in self.stops)
if self.direction is Direction.DOWN:
return any(f < self.floor for f in self.stops)
return False
def step(self):
"""Advance one tick: move one floor and open doors if this floor is a stop."""
if not self.stops:
self.direction = Direction.IDLE
return
if not self._stops_ahead():
self.direction = Direction.DOWN if self.direction is Direction.UP else Direction.UP
self.floor += self.direction.value
if self.floor in self.stops:
self.stops.remove(self.floor)
self.open_doors()
def open_doors(self):
... # door state machine: OPENING → OPEN → CLOSING → CLOSEDDispatching: a strategy
Which elevator should answer a hall call at floor 7 going up? That policy changes between buildings and times of day, so it's a Strategy:
class DispatchStrategy:
def choose(self, elevators, call) -> "Elevator":
raise NotImplementedError
class NearestSuitableElevator(DispatchStrategy):
"""Prefer idle elevators or ones already heading toward the call in the same direction."""
def choose(self, elevators, call):
def cost(e):
distance = abs(e.floor - call.floor)
if e.direction is Direction.IDLE:
return distance
heading_toward = (call.floor - e.floor) * e.direction.value >= 0
if heading_toward and e.direction.value == call.direction.value:
return distance
return distance + 2 * len(e.stops) + 10 # penalize elevators that must turn around
return min(elevators, key=cost)
class ElevatorController:
def __init__(self, elevators, strategy: DispatchStrategy):
self.elevators = elevators
self.strategy = strategy
def hall_call(self, call):
self.strategy.choose(self.elevators, call).add_stop(call.floor)
def car_call(self, elevator_id: int, floor: int):
self.elevators[elevator_id].add_stop(floor)
def tick(self):
for e in self.elevators:
e.step()Design decisions to explain
- Controller vs elevator responsibilities — elevators know how to move and serve their stops; the controller only decides assignment. Each class has one reason to change.
- Dispatch as a strategy — swap in a morning-rush strategy (send idle cars to the lobby) or a zoning strategy (elevators 1–2 serve floors 1–20) without touching other code.
- State pattern for doors — door logic (can't move while open, reopen on obstruction) is a natural state machine.
- Tick-based simulation — makes behavior deterministic and testable.
Extensions
- Capacity and weight — skip elevators that are full when dispatching.
- Emergency / fire mode — a controller-level state that recalls all elevators to the ground floor and ignores calls.
- Reassignment — re-run dispatch periodically, as a better elevator may become available.
- Concurrency — calls arrive from many threads; guard each elevator's stop set, or funnel all calls through a single controller queue.