Engineering Atlas › Low-Level Design

Code an elevator scheduler

12 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

Requirements

Functional:

Non-functional:

Out of scope: weight sensors, fire service modes, destination dispatch keypads and energy optimisation. Name them and put them in Extensibility.

Entities and relationships

Relationships: the system is composed of cars; each car owns one door and its stop set. Requests are transient and get folded into a car's stop set once assigned. The dispatcher reads car state but never mutates it.

Class diagram

classDiagram
    class ElevatorSystem {
      -cars: ElevatorCar[]
      -dispatcher: Dispatcher
      +hallCall(floor, direction) CarId
      +requestFloor(carId, floor)
      +step() Stop[]
    }
    class ElevatorCar {
      +floor: int
      +direction: Direction
      -stops: Set~int~
      -state: CarState
      +addStop(floor)
      +step() int
    }
    class Door {
      +state: DoorState
      +open()
      +close()
    }
    class CarState {
      <<interface>>
      +onTick(car)
      +onRequest(car, floor)
    }
    class Dispatcher {
      <<interface>>
      +pick(cars, floor, direction) CarId
    }
    class Request {
      +floor: int
      +direction: Direction
      +carId: int
    }
    ElevatorSystem "1" *-- "many" ElevatorCar
    ElevatorCar "1" *-- "1" Door
    ElevatorCar --> CarState
    ElevatorSystem --> Dispatcher
    ElevatorSystem ..> Request
    NearestCarDispatcher ..|> Dispatcher
    Idle ..|> CarState
    Moving ..|> CarState
    DoorsOpen ..|> CarState
    OutOfService ..|> CarState

Key flows

A hall call is assigned at once; movement happens only on ticks. Separating "decide" from "move" is what makes the system deterministic.

sequenceDiagram
    participant P as Passenger
    participant B as HallButton
    participant S as ElevatorSystem
    participant D as Dispatcher
    participant C as Car 2
    participant L as HallLantern
    P->>B: press UP at floor 7
    B->>S: hallCall(7, UP)
    S->>D: pick(cars, 7, UP)
    D-->>S: car 2 (cost 3)
    S->>C: addStop(7)
    S-->>L: light UP lantern, car 2
    loop every tick
        S->>C: step()
        C-->>S: moved to next floor or stopped
    end
    C-->>S: stopped at 7
    S-->>L: chime, clear UP light
    P->>C: press 12
    S->>C: addStop(12)

Design patterns used and why

Concurrency and edge cases

Down the stack

Layer 1The contract

Each accepted hall call is assigned to exactly one in-service car, and that car stops at the floor within a bounded number of ticks (at most about two full sweeps, 2F, for LOOK with no new competing requests ahead). A car moves at most one floor per tick and never moves on a tick when it opened doors. Given the same initial state and the same sequence of requests at the same ticks, the system produces the same stops in the same order. That last property is what makes the design testable.

Layer 2The mechanism

Each car keeps a set of stop floors and a direction. On a tick: if the current floor is a stop, serve it; otherwise pick a direction (continue if any stop lies ahead, reverse if not; when idle, go toward the nearest stop) and move one floor. That is LOOK. SCAN differs only in that it travels to the end of the shaft before reversing, which wastes trips in a building. Dispatch scores each car with a cost: distance if the car is idle or already heading toward the call in the call's direction, distance plus a large penalty otherwise, and picks the minimum with ties broken by car id. Real group controllers replace distance with an estimated time to arrival that counts the stops already queued in front of the call, and can reassign calls when a better car frees up.

Layer 3The machine

Real elevators run on programmable logic controllers or embedded boards with a fixed scan cycle: read every input (buttons, floor sensors, door switches), run the logic, write every output, repeat, typically every few to tens of milliseconds. That is exactly the step() model. Floor position comes from sensors in the shaft or encoders on the motor; a variable-frequency drive ramps the motor to limit jerk. A mid-rise car travels around 1 to 2.5 m/s and floors are about 3 to 4 m apart, so a floor passes in a couple of seconds and a door cycle takes several more. The dispatcher's arithmetic takes microseconds, so compute is never the bottleneck. Safety does not depend on any of this software: door interlocks are wired in a hardware safety chain, and a mechanical governor trips the safety gear if the car overspeeds. Software decides where to go; hardware decides whether moving is allowed.

Why it's built this way

  1. Cars are slow and expensive and passengers are impatient, so the order of stops matters more than the speed of the code.
  2. Nearest-first minimises average travel but starves the far floors, so a sweep (LOOK) trades a little average time for a bounded worst case.
  3. Inputs arrive from many places at any time, so a fixed tick that snapshots inputs gives deterministic, testable behaviour, and matches how PLCs already run.
  4. Traffic patterns differ by building and by hour, so dispatch is a swappable strategy rather than code inside the car.
  5. Bugs here can hurt people, so the hardware enforces the safety invariants independently of the scheduler.

Extensibility

Leadership lens

Check yourself

  1. Why does LOOK not starve requests while shortest-seek-first can?

    Show answer

    LOOK only reverses when no stops remain ahead, so any waiting floor is reached within at most two sweeps. Shortest-seek-first always picks the nearest stop, so a steady stream of nearby requests can keep a far floor waiting forever.

  2. Why should the dispatcher penalise a car that is moving away from the call?

    Show answer

    That car must finish its sweep and come back, so its true arrival time is much longer than its distance suggests. Using raw distance would assign calls to cars that cannot serve them soon.

  3. What does a tick-driven step() model buy you over a thread per car with sleeps?

    Show answer

    Determinism: the same inputs at the same ticks produce the same outputs, so tests are exact and recorded traffic can be replayed against a new algorithm. It also matches the scan cycle real controllers use.

  4. Where would you put "never move with the doors open" so that a scheduler bug cannot violate it?

    Show answer

    In the car's state machine, so the moving transition is impossible from the doors-open state, and ultimately in the hardware safety chain, which cuts power regardless of what software asks for.

Watch

Free resources

Open in the interactive app: practice, quiz, mock interview →