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