LLD: Elevator System
Several cars, many floors, and a dispatcher deciding who goes where — state per car, a scheduling strategy, and a step-by-step simulation.
Big idea
An elevator system is a dispatcher plus several cars, each a small state machine. Calls from floors go to the dispatcher, which picks a car by a scheduling strategy; each car keeps a set of stops and moves one floor per tick, continuing in its direction while it has stops ahead (the classic "elevator algorithm"). Separate who serves a call from how a car moves and both become easy to change.
Requirements
Functional
- N cars, F floors.
- Hall calls: someone on a floor presses up or down.
- Car calls: someone inside presses a destination.
- Cars move one floor per tick, stop at requested floors, open doors.
- A dispatcher assigns each hall call to one car.
Non-functional: minimise waiting time; never skip a requested stop; safe under concurrent calls.
Out of scope: weight limits, emergency modes, maintenance (discussed as extensions).
Entities
ElevatorSystem (facade/clock), Dispatcher (assigns hall calls; a Mediator between floors and cars), DispatchStrategy (nearest car, least busy…), Car (floor, direction, stops), Direction (UP, DOWN, IDLE), Request.
Think of it like
A taxi dispatcher with a radio. Customers call the office, not the drivers. The dispatcher looks at where every cab is and which way it's heading and sends the best one. Each driver just keeps a list of pickups and drop-offs and works through them in a sensible order, not zig-zagging across town.
Class diagram
Key flow: a hall call
Code skeleton
class Car:
def __init__(self, cid, floor=0):
self.id, self.floor, self.direction, self.stops = cid, floor, "IDLE", set()
def add_stop(self, floor):
self.stops.add(floor)
def cost(self, floor): # used by the strategy: distance, plus a penalty for moving away
away = (self.direction == "UP" and floor < self.floor) or (self.direction == "DOWN" and floor > self.floor)
return abs(self.floor - floor) + (10 if away else 0) + len(self.stops)
def step(self):
if not self.stops:
self.direction = "IDLE"
return None
if self.floor in self.stops: # arrived
self.stops.discard(self.floor)
return f"car {self.id} opens at {self.floor}"
if self.direction == "IDLE": # start towards the nearest stop
nearest = min(self.stops, key=lambda s: (abs(s - self.floor), s))
self.direction = "UP" if nearest > self.floor else "DOWN"
ahead = [s for s in self.stops if (s > self.floor if self.direction == "UP" else s < self.floor)]
if not ahead: # nothing left this way: reverse (the elevator algorithm)
self.direction = "DOWN" if self.direction == "UP" else "UP"
self.floor += 1 if self.direction == "UP" else -1
return None
class NearestCar: # a DispatchStrategy
def choose(self, cars, floor):
return min(cars, key=lambda c: (c.cost(floor), c.id))
class ElevatorSystem:
def __init__(self, n_cars, strategy):
self.cars = [Car(i) for i in range(n_cars)]
self.strategy, self.time = strategy, 0
def hall_call(self, floor):
car = self.strategy.choose(self.cars, floor)
car.add_stop(floor)
print(f"t={self.time}: call at {floor} -> car {car.id}")
def car_call(self, cid, floor):
self.cars[cid].add_stop(floor)
def tick(self):
self.time += 1
for car in self.cars:
event = car.step()
if event:
print(f"t={self.time}: {event}")
system = ElevatorSystem(2, NearestCar())
system.cars[1].floor = 8
system.hall_call(6)
system.hall_call(1)
for _ in range(3):
system.tick()
system.car_call(1, 0)
system.car_call(0, 4)
for _ in range(9):
system.tick()
print("positions:", [(c.id, c.floor, c.direction) for c in system.cars])output
t=0: call at 6 -> car 1
t=0: call at 1 -> car 0
t=2: car 0 opens at 1
t=3: car 1 opens at 6
t=7: car 0 opens at 4
t=10: car 1 opens at 0
positions: [(0, 4, 'IDLE'), (1, 0, 'IDLE')]Run and edit it in the app.
Each car's movement rule and the dispatcher's choice are independent: try a "least busy" strategy and the cars' code doesn't change.
Watch it
A car at floor 5 going up has stops at 8 and 2. Under the elevator algorithm, what order does it serve them?
Morning rush: most calls are from the lobby going up. Where do you change behaviour to park idle cars at the lobby?
Extensions and trade-offs
- Car state: doors, moving, idle, maintenance → a proper State machine per car.
- Smarter dispatch: estimated-time-of-arrival including stops; destination dispatch (passengers enter destination in the lobby).
- Capacity: skip hall calls when full; the strategy must know load.
- Concurrency: calls arrive from many threads; a single dispatcher loop with a queue avoids races.
- Observability: Observer events for floor displays and logs.
Key terms
- Hall call — A request from a floor button, with a direction.
- Car call — A destination pressed inside a car.
- Elevator algorithm (SCAN) — Keep moving in one direction while there are stops ahead, then reverse.
- Dispatcher — The component that assigns hall calls to cars.