Skip to content
LLD Learn/Case Studies: Physical Systems
Browsing as a guest. Sign in to save your progress and earn XP as you complete chapters.

Design an Elevator System

10 min read

You'll learn to

  • -Design the elevator, request, and scheduler classes, and pick a dispatch strategy (e.g. SCAN/LOOK) via Strategy
  • -Handle multi-elevator coordination and starvation without over-complicating the first-pass design

The elevator system prompt tests something the parking lot prompt does not: a genuinely interesting scheduling algorithm, plus coordination across multiple elevators competing to serve the same building. It rewards candidates who resist the urge to over-engineer the scheduler on the first pass.

Step 1: Requirements and Core Classes

Assume: a building with multiple elevators, each elevator has a current floor and direction, requests come from both inside the elevator (a passenger pressing a floor button) and outside (a hall call pressing up/down on a floor), and the dispatcher must decide which elevator handles which hall call.

The core class shape
from enum import Enum

class Direction(Enum):
    UP = 1
    DOWN = -1
    IDLE = 0

class Request:
    def __init__(self, floor: int, direction: Direction | None = None):
        self.floor = floor
        self.direction = direction   # None for an internal "go to floor" request

class Elevator:
    def __init__(self, elevator_id: int):
        self.id = elevator_id
        self.current_floor = 0
        self.direction = Direction.IDLE
        self._requests: set[int] = set()   # floors this elevator must stop at

    def add_request(self, floor: int) -> None:
        self._requests.add(floor)

    def step(self) -> None:
        if not self._requests:
            self.direction = Direction.IDLE
            return
        target = min(self._requests, key=lambda f: abs(f - self.current_floor))
        self.direction = Direction.UP if target > self.current_floor else Direction.DOWN
        self.current_floor += self.direction.value
        if self.current_floor in self._requests:
            self._requests.remove(self.current_floor)   # arrived, doors open

Step 2: SCAN/LOOK Scheduling as a Strategy

Real elevators do not jump straight to the nearest request - they use SCAN (or the more efficient LOOK variant): continue moving in the current direction, picking up every request along the way, and only reverse once there are no more requests in the current direction. This avoids the "elevator bouncing back and forth" behavior a naive nearest-request approach produces, and it is a natural fit for the Strategy pattern from earlier in this course - the scheduling algorithm is exactly the kind of thing that should be swappable behind a common interface.

SCAN as a swappable SchedulingStrategy
class SchedulingStrategy(ABC):
    @abstractmethod
    def next_floor(self, elevator: Elevator) -> int | None: ...

class ScanStrategy(SchedulingStrategy):
    def next_floor(self, elevator: Elevator) -> int | None:
        requests = elevator._requests
        if not requests:
            return None
        if elevator.direction != Direction.DOWN:
            ahead = [f for f in requests if f >= elevator.current_floor]
            if ahead:
                return min(ahead)          # continue up, picking up requests along the way
        behind = [f for f in requests if f <= elevator.current_floor]
        if behind:
            return max(behind)             # no more requests ahead - reverse direction
        return min(requests, key=lambda f: abs(f - elevator.current_floor))

Step 3: Dispatching Among Multiple Elevators

For a hall call (someone on floor 5 pressing "up"), the Dispatcher needs to decide which of N elevators should respond. A reasonable first-pass heuristic: prefer an elevator already moving in the requested direction and about to pass that floor, then fall back to the closest idle elevator - avoiding sending a fully loaded elevator moving away from the request when a closer, idle one is available.

A simple, defensible dispatch heuristic
class Dispatcher:
    def __init__(self, elevators: list[Elevator]):
        self._elevators = elevators

    def dispatch(self, request: Request) -> Elevator:
        idle = [e for e in self._elevators if e.direction == Direction.IDLE]
        candidates = idle or self._elevators   # prefer idle; fall back to all
        best = min(candidates, key=lambda e: abs(e.current_floor - request.floor))
        best.add_request(request.floor)
        return best

A strong candidate names the trade-off explicitly here rather than pretending the dispatcher is optimal: "this heuristic is deliberately simple - a production system would also weigh current load and passenger wait time, but for this design, closest-idle-first is a defensible, easy-to-reason-about first pass."

Interview Signal is part of Pro

See a real weak answer next to a real strong one for this exact topic.

Quiz is part of Pro

Test what you just read with a short quiz, and bank the XP.

ScaleDojo Logo
Initializing ScaleDojo