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:
- Distance to the requested floor
- 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
- 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.MAINTENANCEto 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.
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”What algorithm do elevators use to decide which floor to go to next?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Real elevators use the SCAN algorithm (also called the elevator algorithm): the elevator travels in one direction (up or down), stopping at every requested floor along the way, until there are no more requests in that direction — then it reverses. LOOK is a variant that reverses at the last requested floor rather than at the physical floor limit, which is more efficient. Both prevent starvation (no floor waits indefinitely) and reduce average wait time compared to FCFS (First Come First Served). FCFS causes backtracking — an elevator could go from floor 1 to 10, back to 3, then back to 8 — while SCAN sweeps in one direction and handles all stops along the way. In multi-elevator systems, the dispatcher assigns incoming requests to the elevator with the lowest cost (distance + direction penalty + current load).”}},{“@type”:”Question”,”name”:”How do you model the elevator state machine in an OOP interview?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Use two enums: Direction (UP, DOWN, IDLE) and ElevatorState (MOVING, STOPPED, MAINTENANCE). Each Elevator stores: current_floor, direction, state, and a destinations set (floors to stop at). Key methods: add_destination(floor) adds a floor to the set; move() advances one floor in the current direction; step() processes one time unit — if current floor is in destinations, open doors (remove from set, set state=STOPPED), then call move() if destinations remain. The SCAN algorithm is in move(): find floors above if going UP, change direction to DOWN if none; find floors below if going DOWN, change direction to UP if none. The ElevatorSystem orchestrator dispatches requests using cost_to_serve() scoring and calls step() on all elevators each time unit.”}},{“@type”:”Question”,”name”:”How does multi-elevator dispatching work in a building elevator system?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”When an external request arrives (hallway button pressed at floor F in direction D), the dispatcher scores all available elevators and assigns the one with minimum cost. Cost function: distance = abs(elevator.current_floor – F); add a direction_penalty (e.g., 20 steps) if the elevator is currently moving in the opposite direction to D; add a load_penalty proportional to the number of pending destinations. An elevator already traveling UP toward floor F for an UP request is nearly free to pick up (just add F to its existing path). An IDLE elevator has zero direction penalty. The assignment adds F to the chosen elevator’s destinations set — the SCAN algorithm will service it in order. This greedy assignment is not globally optimal but performs well in practice and is the expected answer in interviews.”}}]}
🏢 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: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering