Low-Level Design: Elevator System (OOP Interview)

Low-Level Design: Elevator System

The elevator system is a classic OOP interview problem testing state machines, dispatching algorithms, and clean class design. The interviewer wants to see: how you model the core entities, how requests are dispatched across multiple elevators, and how the elevator decides which floors to service in what order.

Core Classes

Direction and State Enums

from enum import Enum

class Direction(Enum):
    UP = "UP"
    DOWN = "DOWN"
    IDLE = "IDLE"

class ElevatorState(Enum):
    MOVING = "MOVING"
    STOPPED = "STOPPED"
    MAINTENANCE = "MAINTENANCE"

Request

from dataclasses import dataclass

@dataclass
class ExternalRequest:
    """Button pressed in the hallway."""
    floor: int
    direction: Direction

@dataclass
class InternalRequest:
    """Button pressed inside the elevator."""
    floor: int

Elevator

class Elevator:
    def __init__(self, elevator_id: int, total_floors: int):
        self.elevator_id = elevator_id
        self.current_floor = 1
        self.direction = Direction.IDLE
        self.state = ElevatorState.STOPPED
        self.destinations = set()   # floors to stop at

    def add_destination(self, floor: int) -> None:
        self.destinations.add(floor)

    def move(self) -> None:
        """Advance one floor in current direction (SCAN algorithm)."""
        if not self.destinations:
            self.direction = Direction.IDLE
            self.state = ElevatorState.STOPPED
            return

        if self.direction == Direction.UP or self.direction == Direction.IDLE:
            above = [f for f in self.destinations if f > self.current_floor]
            if above:
                self.direction = Direction.UP
                self.state = ElevatorState.MOVING
                self.current_floor += 1
            else:
                self.direction = Direction.DOWN
                self.move()  # re-evaluate downward
        else:
            below = [f for f in self.destinations if f  list[int]:
        """
        Process one time step. Move toward next destination,
        stop if current floor is a destination.
        Returns list of floors where doors opened this step.
        """
        opened = []
        if self.current_floor in self.destinations:
            self.destinations.remove(self.current_floor)
            self.state = ElevatorState.STOPPED
            opened.append(self.current_floor)

        if self.destinations:
            self.move()

        return opened

    def cost_to_serve(self, request_floor: int, direction: Direction) -> int:
        """
        Estimate steps needed to serve this request.
        Used by dispatcher for assignment scoring.
        """
        distance = abs(self.current_floor - request_floor)
        # Penalize if elevator is going in opposite direction
        if self.direction != Direction.IDLE and self.direction != direction:
            return distance + 20  # detour penalty
        return distance

ElevatorSystem (Dispatcher)

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

    def request_elevator(self, floor: int, direction: Direction) -> None:
        """External request: person on a floor presses UP or DOWN."""
        best = min(
            self.elevators,
            key=lambda e: e.cost_to_serve(floor, direction)
        )
        best.add_destination(floor)
        print(f"Elevator {best.elevator_id} assigned to floor {floor} ({direction.value})")

    def select_floor(self, elevator_id: int, floor: int) -> None:
        """Internal request: person inside elevator presses a floor button."""
        if not (1 <= floor  None:
        """Advance simulation by one time step."""
        for elevator in self.elevators:
            opened = elevator.step()
            for floor in opened:
                print(f"Elevator {elevator.elevator_id} doors open at floor {floor}")

    def status(self) -> list[dict]:
        return [
            {
                'id': e.elevator_id,
                'floor': e.current_floor,
                'direction': e.direction.value,
                'state': e.state.value,
                'destinations': sorted(e.destinations),
            }
            for e in self.elevators
        ]

Dispatching Algorithm: SCAN (Elevator Algorithm)

The SCAN algorithm is how real elevators work: the elevator travels in one direction, servicing all requests along the way, then reverses at the extreme. This minimizes average wait time and prevents starvation (unlike FCFS).

  • FCFS (First Come First Served): Simple but causes backtracking — elevator might go from floor 1 to 10, then back to 3, then to 8.
  • SCAN: Go in one direction until no more requests in that direction, then reverse. Serves requests in order of floor number within each direction.
  • LOOK: Like SCAN but reverses at the last requested floor (not at the physical extreme). More efficient than SCAN.

Multi-Elevator Dispatching

The cost_to_serve() method is the key for assigning requests. In a real system, the score combines:

  1. Distance to the requested floor
  2. Direction alignment: An elevator already going up toward floor 7 is a better choice for a floor-7-up request than an elevator going down
  3. Load: An elevator with 0 destinations is preferred over one with 10 pending stops

A simple heuristic: cost = |current_floor – request_floor| + (20 if direction_mismatch else 0) + len(destinations) * 2. Assign the request to the elevator with the minimum cost.

Usage Example

system = ElevatorSystem(num_elevators=3, num_floors=20)

# External requests (hallway buttons)
system.request_elevator(floor=5, direction=Direction.UP)
system.request_elevator(floor=12, direction=Direction.DOWN)

# Internal request (inside elevator 0)
system.select_floor(elevator_id=0, floor=15)

# Simulate
for _ in range(20):
    system.step()

print(system.status())

Interview Follow-ups

  • Emergency mode: Add a priority level to requests. Fire alarm requests override all others and all elevators go to floor 1.
  • Maintenance mode: Set ElevatorState.MAINTENANCE to exclude an elevator from dispatching without removing it from the system.
  • Weight sensor: Elevator tracks current load; if at capacity (e.g., 80% full), skip external requests but still serve internal requests (passengers inside).
  • Why SCAN over FCFS? SCAN reduces average wait time and prevents starvation. FCFS can cause an elevator to thrash between floors 1 and 20 on every request.

🏢 Asked at: Atlassian Interview Guide

🏢 Asked at: Shopify Interview Guide

🏢 Asked at: Apple Interview Guide 2026: iOS Systems, Hardware-Software Integration, and iCloud Architecture

🏢 Asked at: Uber Interview Guide 2026: Dispatch Systems, Geospatial Algorithms, and Marketplace Engineering

🏢 Asked at: Airbnb Interview Guide 2026: Search Systems, Trust and Safety, and Full-Stack Engineering

Asked at: LinkedIn Interview Guide

See also: Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence

See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering

Scroll to Top