Search…
Low Level Design · Part 8

Designing an elevator system

In this series (15 parts)
  1. Introduction to low level design
  2. SOLID principles
  3. Design patterns: Creational
  4. Design patterns: Structural
  5. Design patterns: Behavioral
  6. Designing a parking lot
  7. Designing a library management system
  8. Designing an elevator system
  9. Designing a hotel booking system
  10. Designing a ride-sharing model
  11. Designing a rate limiter
  12. Designing a logging framework
  13. Designing a notification system
  14. API design and contract-first development
  15. Data modeling for system design

An elevator system is one of the most rewarding LLD problems because it forces you to think about real-time state, scheduling under constraints, and multi-entity coordination. The core challenge is not “make a box go up and down.” It is deciding which floor to visit next when ten people press buttons at the same time.

Requirements

Functional:

  1. A building has N floors and M elevators.
  2. Each floor has up/down call buttons. Each elevator cabin has floor-selection buttons.
  3. The system dispatches the optimal elevator to answer a floor call.
  4. Elevators move in one direction until there are no more requests in that direction, then reverse (LOOK algorithm).
  5. Doors open automatically on arrival, close after a timeout.
  6. Emergency stop overrides all scheduling.

Non-functional:

  • Minimize average wait time across all passengers.
  • The system must handle concurrent button presses safely.
  • Adding a new scheduling algorithm should not require rewriting the controller.

Entities

EntityResponsibility
ElevatorTracks current floor, direction, door state, requests
ElevatorControllerReceives external requests, dispatches to elevators
RequestRepresents a floor call or cabin button press
DirectionEnum: UP, DOWN, IDLE
DoorStateEnum: OPEN, CLOSED, OPENING, CLOSING

Class diagram

classDiagram
  class ElevatorController {
      -List~Elevator~ elevators
      -SchedulingStrategy strategy
      +handleFloorRequest(Request) void
      +handleCabinRequest(int elevatorId, int floor) void
      +step() void
  }

  class Elevator {
      -int id
      -int currentFloor
      -Direction direction
      -DoorState doorState
      -TreeSet~Integer~ upQueue
      -TreeSet~Integer~ downQueue
      +addRequest(int floor, Direction dir) void
      +move() void
      +openDoors() void
      +closeDoors() void
      +isIdle() boolean
  }

  class Request {
      -int floor
      -Direction direction
      -long timestamp
  }

  class SchedulingStrategy {
      <<interface>>
      +selectElevator(List~Elevator~ elevators, Request request) Elevator
  }

  class LOOKStrategy {
      +selectElevator(List~Elevator~ elevators, Request request) Elevator
  }

  class ShortestSeekStrategy {
      +selectElevator(List~Elevator~ elevators, Request request) Elevator
  }

  class Direction {
      <<enumeration>>
      UP
      DOWN
      IDLE
  }

  class DoorState {
      <<enumeration>>
      OPEN
      CLOSED
      OPENING
      CLOSING
  }

  ElevatorController "1" *-- "many" Elevator
  ElevatorController --> SchedulingStrategy
  SchedulingStrategy <|.. LOOKStrategy
  SchedulingStrategy <|.. ShortestSeekStrategy
  Elevator --> Direction
  Elevator --> DoorState
  ElevatorController ..> Request

Class diagram for the elevator system.

Elevator state machine

An individual elevator cycles through a small set of states. The transitions depend on the request queue and the current floor.

stateDiagram-v2
  [*] --> Idle
  Idle --> MovingUp : request above
  Idle --> MovingDown : request below
  Idle --> DoorsOpen : request at current floor
  MovingUp --> DoorsOpen : arrived at requested floor
  MovingDown --> DoorsOpen : arrived at requested floor
  DoorsOpen --> DoorsClosed : timeout
  DoorsClosed --> MovingUp : more requests above
  DoorsClosed --> MovingDown : more requests below
  DoorsClosed --> Idle : no pending requests
  MovingUp --> EmergencyStop : emergency
  MovingDown --> EmergencyStop : emergency
  DoorsOpen --> EmergencyStop : emergency
  EmergencyStop --> Idle : reset

State diagram for a single elevator.

Notice that DoorsOpen is a transient state. The elevator does not stay there indefinitely; a timeout triggers the transition to DoorsClosed, and the scheduler then decides the next move.

The LOOK algorithm

LOOK is the standard elevator scheduling algorithm. It works like the disk-arm SCAN algorithm but reverses direction when there are no more requests ahead, rather than traveling all the way to the end.

How it works:

  1. The elevator moves in its current direction.
  2. At each floor, if there is a request for this floor in the current direction, stop and open doors.
  3. After closing doors, continue in the same direction if there are more requests ahead.
  4. If no more requests ahead, reverse direction.
  5. If no requests in either direction, go idle.
public void move() {
    if (direction == Direction.UP) {
        Integer next = upQueue.higher(currentFloor);
        if (next != null) {
            currentFloor = next;
            upQueue.remove(next);
            openDoors();
        } else if (!downQueue.isEmpty()) {
            direction = Direction.DOWN;
            move();
        } else {
            direction = Direction.IDLE;
        }
    } else if (direction == Direction.DOWN) {
        Integer next = downQueue.lower(currentFloor);
        if (next != null) {
            currentFloor = next;
            downQueue.remove(next);
            openDoors();
        } else if (!upQueue.isEmpty()) {
            direction = Direction.UP;
            move();
        } else {
            direction = Direction.IDLE;
        }
    }
}

Using TreeSet for the request queues gives O(log n) insertion and O(log n) nearest-floor lookup via higher() and lower().

Request handling sequence

When someone presses the “Up” button on floor 3, here is the full sequence of events:

sequenceDiagram
  participant P as Passenger
  participant FC as FloorCallPanel
  participant EC as ElevatorController
  participant S as SchedulingStrategy
  participant E as Elevator

  P->>FC: Press UP on floor 3
  FC->>EC: handleFloorRequest(floor=3, dir=UP)
  EC->>S: selectElevator(elevators, request)
  S-->>EC: elevator #2
  EC->>E: addRequest(floor=3, dir=UP)
  E->>E: move() towards floor 3
  E->>E: openDoors()
  P->>E: Enter cabin, press floor 7
  E->>E: addRequest(floor=7, dir=UP)
  E->>E: closeDoors()
  E->>E: move() towards floor 7
  E->>E: openDoors()
  P->>E: Exit cabin

Sequence diagram showing a complete ride from floor call to destination.

Scheduling strategies

The Strategy pattern makes it easy to swap scheduling algorithms.

Shortest Seek Time First (SSTF): Pick the elevator closest to the requesting floor. Simple, but can starve distant floors.

public class ShortestSeekStrategy implements SchedulingStrategy {
    public Elevator selectElevator(List<Elevator> elevators, Request request) {
        return elevators.stream()
            .min(Comparator.comparingInt(e ->
                Math.abs(e.getCurrentFloor() - request.getFloor())))
            .orElseThrow();
    }
}

LOOK-aware dispatch: Prefer an elevator already moving toward the requesting floor in the right direction. Fall back to the nearest idle elevator.

public class LOOKStrategy implements SchedulingStrategy {
    public Elevator selectElevator(List<Elevator> elevators, Request request) {
        // Prefer elevators moving toward the request
        Optional<Elevator> moving = elevators.stream()
            .filter(e -> isMovingToward(e, request))
            .min(Comparator.comparingInt(e ->
                Math.abs(e.getCurrentFloor() - request.getFloor())));
        if (moving.isPresent()) return moving.get();

        // Fall back to nearest idle
        return elevators.stream()
            .filter(Elevator::isIdle)
            .min(Comparator.comparingInt(e ->
                Math.abs(e.getCurrentFloor() - request.getFloor())))
            .orElse(elevators.get(0));
    }
}

Concurrency considerations

Button presses are asynchronous events. Multiple passengers press buttons on different floors simultaneously. The ElevatorController must be thread-safe.

Request queue thread safety: Each elevator’s upQueue and downQueue should use ConcurrentSkipListSet or be guarded by a lock.

Controller dispatch: The handleFloorRequest method should be synchronized or use a concurrent queue that the controller drains on each tick.

Simulation loop: In a real system, the controller runs an event loop. Each tick, it processes pending requests and calls move() on each elevator. This is the Command pattern applied to scheduling.

public void step() {
    for (Elevator elevator : elevators) {
        if (elevator.isDoorOpen()) {
            elevator.closeDoors();
        } else {
            elevator.move();
        }
    }
}

Extensibility

ChangeImpact
Add express elevatorsSubclass Elevator, override canStop(floor)
Add weight-based limitsNew field on Elevator, check before opening
Add VIP priority schedulingNew SchedulingStrategy implementation
Add floor access restrictionsAccess control list on Elevator or Floor

What comes next

The hotel booking system shifts from real-time control to transactional design. You will deal with availability calendars, double-booking prevention, and cancellation policies, problems where the data model matters more than the scheduling algorithm.

Start typing to search across all content
navigate Enter open Esc close