Low-Level Design: Elevator System (OOP Design Interview)

Elevator System Low-Level Design

Designing an elevator system is a classic low-level design (LLD) interview question asked at companies like Amazon, Google, and Microsoft. It tests your ability to model real-world systems with OOP, apply design patterns, and handle concurrency and scheduling algorithms.

Requirements Clarification

Functional Requirements

  • Multiple elevators in a building with N floors
  • External requests: passengers press UP/DOWN buttons on each floor
  • Internal requests: passengers press destination floor buttons inside elevator
  • Elevator displays current floor and direction
  • Doors open automatically when elevator reaches destination, close after timeout
  • Emergency stop and alarm button

Non-Functional Requirements

  • Minimize passenger wait time
  • Minimize total travel distance
  • Handle concurrent requests safely
  • Support for future: capacity limits, priority floors (VIP), express elevators

Core Classes and Enums

from enum import Enum
from collections import defaultdict
import threading
import heapq

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

class DoorState(Enum):
    OPEN = "OPEN"
    CLOSED = "CLOSED"

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

class ButtonType(Enum):
    INTERNAL = "INTERNAL"  # inside elevator
    EXTERNAL = "EXTERNAL"  # on floor panel

class Button:
    def __init__(self, floor: int, button_type: ButtonType, direction: Direction = None):
        self.floor = floor
        self.button_type = button_type
        self.direction = direction  # only for external buttons
        self.is_pressed = False

    def press(self):
        self.is_pressed = True

    def reset(self):
        self.is_pressed = False

class FloorPanel:
    """External panel on each floor with UP and DOWN buttons"""
    def __init__(self, floor: int, is_top: bool = False, is_bottom: bool = False):
        self.floor = floor
        self.up_button = Button(floor, ButtonType.EXTERNAL, Direction.UP) if not is_top else None
        self.down_button = Button(floor, ButtonType.EXTERNAL, Direction.DOWN) if not is_bottom else None

Elevator Class

class Elevator:
    def __init__(self, elevator_id: int, total_floors: int):
        self.id = elevator_id
        self.current_floor = 1
        self.direction = Direction.IDLE
        self.state = ElevatorState.STOPPED
        self.door_state = DoorState.CLOSED
        self.capacity = 10
        self.current_passengers = 0

        # SCAN algorithm: two heaps for floors to visit
        self.up_stops = []    # min-heap: floors above current
        self.down_stops = []  # max-heap (negated): floors below current
        self.lock = threading.Lock()

    def add_stop(self, floor: int):
        with self.lock:
            if floor > self.current_floor:
                if floor not in self.up_stops:
                    heapq.heappush(self.up_stops, floor)
            elif floor  int:
        """SCAN (elevator) algorithm: continue in current direction, reverse when done"""
        if self.direction == Direction.UP:
            if self.up_stops:
                return heapq.heappop(self.up_stops)
            elif self.down_stops:
                self.direction = Direction.DOWN
                return -heapq.heappop(self.down_stops)
        elif self.direction == Direction.DOWN:
            if self.down_stops:
                return -heapq.heappop(self.down_stops)
            elif self.up_stops:
                self.direction = Direction.UP
                return heapq.heappop(self.up_stops)
        else:  # IDLE
            if self.up_stops:
                self.direction = Direction.UP
                return heapq.heappop(self.up_stops)
            elif self.down_stops:
                self.direction = Direction.DOWN
                return -heapq.heappop(self.down_stops)
        return self.current_floor

    def move_to(self, target_floor: int):
        """Simulate movement to target floor"""
        while self.current_floor != target_floor:
            if self.current_floor  int:
        """Estimate cost for dispatcher to assign requests"""
        distance = abs(self.current_floor - floor)
        # Penalty if elevator must reverse direction
        direction_penalty = 0
        if self.direction != Direction.IDLE and self.direction != direction:
            direction_penalty = 20
        return distance + direction_penalty

Dispatcher: Scheduling Algorithms

class ElevatorDispatcher:
    """
    Assigns floor requests to the optimal elevator.
    Strategy: Nearest Car with directional preference (LOOK algorithm variant)
    """
    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
        self.lock = threading.Lock()

    def request_elevator(self, floor: int, direction: Direction):
        """Handle external button press — find best elevator"""
        best_elevator = self._find_best_elevator(floor, direction)
        best_elevator.add_stop(floor)

    def _find_best_elevator(self, floor: int, direction: Direction) -> Elevator:
        """
        Scoring heuristic:
        1. Idle elevator: score = distance
        2. Moving toward request in same direction: score = distance
        3. Moving away or opposite direction: score = distance + penalty
        """
        best = min(self.elevators, key=lambda e: e.cost_to_serve(floor, direction))
        return best

    def select_floor(self, elevator_id: int, floor: int):
        """Handle internal button press (passenger selects destination)"""
        elevator = self.elevators[elevator_id]
        elevator.add_stop(floor)

    def run_elevator(self, elevator: Elevator):
        """Main elevator loop — runs in separate thread per elevator"""
        while True:
            next_floor = elevator.next_stop()
            if next_floor != elevator.current_floor:
                elevator.move_to(next_floor)
            else:
                elevator.direction = Direction.IDLE
                elevator.state = ElevatorState.STOPPED
                # Wait for next request

Building Class (Facade)

class Building:
    def __init__(self, num_floors: int, num_elevators: int):
        self.num_floors = num_floors
        self.floor_panels = {
            i: FloorPanel(
                floor=i,
                is_top=(i == num_floors),
                is_bottom=(i == 1)
            )
            for i in range(1, num_floors + 1)
        }
        self.dispatcher = ElevatorDispatcher(num_elevators, num_floors)

    def press_up_button(self, floor: int):
        panel = self.floor_panels[floor]
        if panel.up_button:
            panel.up_button.press()
            self.dispatcher.request_elevator(floor, Direction.UP)

    def press_down_button(self, floor: int):
        panel = self.floor_panels[floor]
        if panel.down_button:
            panel.down_button.press()
            self.dispatcher.request_elevator(floor, Direction.DOWN)

    def press_floor_button(self, elevator_id: int, floor: int):
        """Passenger inside elevator selects destination"""
        self.dispatcher.select_floor(elevator_id, floor)

    def get_elevator_status(self):
        for e in self.dispatcher.elevators:
            print(f"Elevator {e.id}: Floor {e.current_floor}, "
                  f"Direction {e.direction.value}, "
                  f"State {e.state.value}, "
                  f"Door {e.door_state.value}")

# Usage
building = Building(num_floors=20, num_elevators=4)
building.press_up_button(5)    # Someone on floor 5 wants to go up
building.press_down_button(15) # Someone on floor 15 wants to go down
building.press_floor_button(0, 18)  # Passenger in elevator 0 selects floor 18
building.get_elevator_status()

Scheduling Algorithms

SCAN Algorithm (Elevator Algorithm)

The elevator moves in one direction, services all stops, then reverses. Like a physical elevator: if going UP, service all UP requests; at top, switch to DOWN. Implemented above with two heaps (min-heap for up stops, max-heap for down stops). Trade-off: Starvation possible for floors at extremes when load is heavy in the middle.

LOOK Algorithm

Variant of SCAN: elevator only goes as far as the highest/lowest requested floor, then reverses (doesn’t travel all the way to top/bottom). More efficient than SCAN in practice. Use the heap-based implementation above — it naturally implements LOOK since we pop the next actual stop rather than traversing all floors.

Nearest Car (NC)

Assign each external request to the elevator with the lowest cost (distance + direction penalty). Simple and works well for light loads. Implemented in _find_best_elevator(). Trade-off: Can cause “elevator chasing” under heavy load.

Design Patterns Used

  • State Pattern: Elevator has states (MOVING, STOPPED, MAINTENANCE) with different allowed transitions
  • Observer Pattern: Floor panels observe elevator position to update displays
  • Strategy Pattern: Dispatcher uses interchangeable scheduling algorithms (SCAN, LOOK, NC)
  • Facade Pattern: Building class provides a simplified interface to the elevator subsystem
  • Command Pattern: Button presses as command objects (allows undo, logging, queuing)

Concurrency Considerations

  • Each elevator runs in its own thread with a threading.Lock protecting the stop queues
  • Dispatcher uses a lock when modifying elevator assignments
  • In production: use actor model (Akka) or message queue per elevator to eliminate shared state
  • Door state machine must be atomic: CLOSING → detect obstacle → OPENING (no half-states)

Interview Tips

  • Start with requirements: how many floors, how many elevators, capacity constraints?
  • Identify core entities: Elevator, Floor, Button, Dispatcher — draw UML class diagram
  • Discuss scheduling algorithm explicitly — interviewers want to hear SCAN/LOOK
  • Mention thread safety for the stop queue and door state machine
  • Extension: emergency stops, maintenance mode, weight sensors, express elevators
  • Common companies: Amazon, Google, Microsoft, Uber, Lyft
Scroll to Top