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 → CLOSED

Dispatching: 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.