skip to content

Designing an Elevator System

Updated: at 12:00 AM

We’re designing a simplified elevator control system for a single building. The system must assign elevators to pickup requests and move them efficiently across floors while serving passenger drop-offs.

To keep the scope focused, this system operates entirely in memory and assumes a fixed number of floors and elevators. There is no persistence, no distributed coordination, and no modeling of real-world details like door timing, passenger capacity, or hardware faults. Movement is simulated in discrete steps rather than continuous motion.

The API exposed by the controller is:

Here are some ideas you might have

Always choose the nearest elevator

A naive approach is assigning the elevator with the smallest absolute distance. The problems with this approach are:

First available elevator

Another idea is assigning the first idle elevator. The problems are:

What must always be true

Before designing the system, define correctness rules.

  1. Elevators must only move within building floor bounds
  2. Pickup requests must eventually be served
  3. Elevators should continue in their current direction while requests exist in that direction
  4. Direction changes occur only when no requests remain in the current direction
  5. The most appropriate elevator should serve each pickup request

Core Scheduling Idea

The system models each elevator as a stateful agent that maintains its own request queues. Requests are separated by direction up_stops and down_stops

This mirrors how real elevators operate:

This prevents inefficient zig-zag movement.

State Model

Elevator
- id
- current_floor
- direction
- up_stops
- down_stops

ElevatorController
- elevators

Elevator

An elevator represents an independent moving unit.

State:

Requests are stored in sets because:

ElevatorController

The controller coordinates multiple elevators. The responsibilities are:

Request Types

Pickup request

Generated when a passenger presses a hall button. The controller chooses the best elevator and inserts the stop into that elevator’s queue.

Dropoff request

Generated inside the elevator. The destination floor is added to the elevator’s stops based on direction.

Choosing the Best Elevator

Each elevator computes a distance score representing how costly it would be to serve the request. The distance depends on:

Case 1: Elevator moving toward request

Example:

Elevator: floor 3 → going UP
Request: floor 6 → UP

Distance is floor - current_floor. The elevator can serve it naturally.

Case 2: Elevator moving away from request

Example:

Elevator: floor 6 → going UP
Request: floor 3 → DOWN

The elevator must:

  1. Finish current upward stops
  2. Reverse direction
  3. Come back down

Distance becomes distance_to_top + distance_back

Case 3: Elevator idle

Idle elevators simply use absolute distance abs(current_floor - request_floor)

Simulation Loop

The controller advances the system with step().

Algorithm:

  1. Iterate through all elevators
  2. Execute one step for each elevator
  3. Elevators update their own state independently

This creates a simple time-based simulation of elevator movement.

Minimal Class Diagram

classDiagram

class Elevator {
    id: int
    current_floor: int
    direction: Direction
    up_stops: Set
    down_stops: Set
    add_pickup()
    add_dropoff()
    step()
}

class ElevatorController {
    elevators: List~Elevator~
    request_pickup()
    request_dropoff()
    step()
}

ElevatorController o-- Elevator

Implementation (Python)

from typing import List, Literal, Set

type Direction = Literal["UP", "DOWN", "IDLE"]
NUM_FLOORS = 10

class Elevator:
    def __init__(self, eid: int):
        self.id = eid
        self.current_floor: int = 0
        self.direction: Direction = "IDLE"

        self.up_stops: Set[int] = set()
        self.down_stops: Set[int] = set()

    def add_dropoff(self, floor: int) -> None:
        if not (0 <= floor < NUM_FLOORS):
            return

        if floor > self.current_floor:
            self.up_stops.add(floor)
        elif floor < self.current_floor:
            self.down_stops.add(floor)

    def add_pickup(self, floor: int, direction: Direction) -> None:
        if not (0 <= floor < NUM_FLOORS):
            return

        if direction == "UP":
            self.up_stops.add(floor)
        elif direction == "DOWN":
            self.down_stops.add(floor)

    def distance(self, floor: int, direction: Direction) -> int:
        if self.direction == "UP":
            if floor >= self.current_floor and direction == "UP":
                return floor - self.current_floor

            top = max(self.up_stops, default=self.current_floor)
            return (top - self.current_floor) + abs(top - floor)

        if self.direction == "DOWN":
            if floor <= self.current_floor and direction == "DOWN":
                return self.current_floor - floor

            bottom = min(self.down_stops, default=self.current_floor)
            return (self.current_floor - bottom) + abs(bottom - floor)

        return abs(self.current_floor - floor)

    def step(self) -> None:
        self._serve_floor()
        self._move()
        self._update_direction()

    def _move(self) -> None:
        if self.direction == "UP":
            if self.current_floor < NUM_FLOORS - 1:
                self.current_floor += 1

        elif self.direction == "DOWN":
            if self.current_floor > 0:
                self.current_floor -= 1

    def _serve_floor(self) -> None:
        if self.direction == "UP":
            if self.current_floor in self.up_stops:
                self._open()
                self.up_stops.remove(self.current_floor)
                self._close()

        elif self.direction == "DOWN":
            if self.current_floor in self.down_stops:
                self._open()
                self.down_stops.remove(self.current_floor)
                self._close()

        elif self.direction == "IDLE":
            if self.current_floor in self.up_stops:
                self._open()
                self.up_stops.remove(self.current_floor)
                self._close()
            elif self.current_floor in self.down_stops:
                self._open()
                self.down_stops.remove(self.current_floor)
                self._close()

    def _update_direction(self) -> None:
        if self.direction == "UP":
            if self.up_stops:
                return
            if self.down_stops:
                self.direction = "DOWN"
            else:
                self.direction = "IDLE"

        elif self.direction == "DOWN":
            if self.down_stops:
                return
            if self.up_stops:
                self.direction = "UP"
            else:
                self.direction = "IDLE"

        else:
            if self.up_stops:
                self.direction = "UP"
            elif self.down_stops:
                self.direction = "DOWN"

    def _open(self) -> None:
        pass

    def _close(self) -> None:
        pass


class ElevatorController:
    def __init__(self, num_elevators: int = 3):
        self.elevators: List[Elevator] = [
            Elevator(i) for i in range(num_elevators)
        ]

    def request_pickup(self, floor: int, direction: Direction) -> None:
        if not (0 <= floor < NUM_FLOORS):
            return

        best_elevator = None
        best_distance = float("inf")

        for elevator in self.elevators:
            dist = elevator.distance(floor, direction)
            if dist < best_distance:
                best_distance = dist
                best_elevator = elevator

        if best_elevator:
            best_elevator.add_pickup(floor, direction)

    def request_dropoff(self, elevator_id: int, floor: int) -> None:
        if not (0 <= elevator_id < len(self.elevators)):
            return

        self.elevators[elevator_id].add_dropoff(floor)

    def step(self) -> None:
        for elevator in self.elevators:
            elevator.step()

Limits and Extensions

This model captures the basic mechanics of elevator scheduling, but real systems are more complex.

Passenger capacity is one common extension. Elevators must consider how many passengers they can carry before accepting new requests.

Another extension is smarter dispatch logic. Modern systems often predict traffic patterns and group requests heading in the same direction.

Large buildings also introduce strategies like zoning or express elevators that skip certain floors to reduce travel time.

Despite these additions, the core idea remains the same: elevators maintain directional stop queues and serve them efficiently before reversing direction.