Elevator System
Introduction
Section titled “Introduction”In this comprehensive case study, we’ll design an Elevator System from scratch using the systematic 8-step approach. This is a classic LLD interview problem that demonstrates state management, concurrent processing, and multiple design patterns working together.
Problem Statement
Section titled “Problem Statement”Design an Elevator System for a high-rise building that can:
- Support multiple elevators, each serving all floors
- Enforce capacity constraints (max people per elevator)
- Handle two types of requests: external (from floor buttons) and internal (from inside elevator)
- Intelligently dispatch requests to minimize waiting time
- Process multiple requests per elevator efficiently
- Handle concurrent requests from multiple users safely
Step 1: Clarify Requirements
Section titled “Step 1: Clarify Requirements”Before designing, let’s ask the right questions!
Clarifying Questions
Section titled “Clarifying Questions”Functional Requirements:
- Elevator Count: How many elevators? → Multiple, configurable
- Floors: How many floors? → All elevators serve all floors
- Capacity: Maximum people per elevator? → Yes, configurable per elevator
- Request Types: External (floor buttons) and internal (inside elevator)? → Yes, both
- Dispatching: How to choose elevator? → Based on direction, distance, load
- Request Processing: Process requests in order? → Yes, optimize for efficiency
Non-Functional Requirements:
- Concurrency: Multiple simultaneous requests? → Yes, thread-safe operations needed
- Performance: Response time expectations? → Fast dispatch, minimal waiting
- Scalability: How many requests? → Handle hundreds of requests
Edge Cases to Consider:
- What if all elevators are busy?
- What if elevator is at capacity?
- What if request comes while elevator is moving?
- What if multiple elevators are equidistant?
- What if elevator breaks down?
Requirements Summary
Section titled “Requirements Summary”Step 2: Identify Actors
Section titled “Step 2: Identify Actors”Actors are external entities that interact with the system.
Who Uses This System?
Section titled “Who Uses This System?”Primary Actors:
- User/Passenger - Requests elevator from floor, selects destination inside elevator
- Maintenance Staff - (Future) Monitors system health, performs maintenance
- Building Management - (Future) Configures system parameters, sets capacity
Secondary Actors:
- Emergency System - (Future) Triggers emergency stops
- Monitoring System - (Future) Tracks usage, performance metrics
Actor Interactions
Section titled “Actor Interactions”Step 3: Identify Entities
Section titled “Step 3: Identify Entities”Entities are core objects in your system that have data and behavior.
The Noun Hunt
Section titled “The Noun Hunt”Looking at the requirements, we identify these core entities:
- ElevatorSystem - Main orchestrator, dispatches requests
- Elevator - Individual elevator car with state and requests
- Request - Represents a floor request (external or internal)
- ElevatorState - Abstract state (Idle, MovingUp, MovingDown)
- Display - Shows elevator status to users
- Direction - Enum for movement direction
Entity Relationships
Section titled “Entity Relationships”Each class should have a single, well-defined responsibility (SRP).
Responsibility Assignment
Section titled “Responsibility Assignment”ElevatorSystem:
- Manage collection of elevators
- Dispatch requests to appropriate elevator
- Use strategy to select best elevator
- Coordinate elevator operations
Elevator:
- Manage its own state (idle, moving up, moving down)
- Process requests in its queue
- Move between floors
- Notify observers of state changes
ElevatorState:
- Define behavior for specific state
- Handle request addition based on state
- Control movement logic
- Transition to other states when appropriate
Request:
- Store request information (floor, direction, source)
- Provide request details to elevator
Display:
- Observe elevator state changes
- Update display information
- Show current floor and direction
Responsibility Visualization
Section titled “Responsibility Visualization”Step 5: Design Class Diagrams
Section titled “Step 5: Design Class Diagrams”Class diagrams show structure, relationships, and design patterns.
classDiagram
class ElevatorSystem {
-ElevatorSystem instance
-Map~Integer,Elevator~ elevators
-ElevatorSelectionStrategy selectionStrategy
-ExecutorService executorService
+getInstance() ElevatorSystem
+addElevator(Elevator) void
+requestElevator(int, Direction) void
+setSelectionStrategy(ElevatorSelectionStrategy) void
}
class Elevator {
-int id
-AtomicInteger currentFloor
-ElevatorState state
-TreeSet~Integer~ upRequests
-TreeSet~Integer~ downRequests
-List~ElevatorObserver~ observers
-boolean isRunning
+addObserver(ElevatorObserver) void
+addRequest(Request) void
+move() void
+run() void
+notifyObservers() void
+setState(ElevatorState) void
}
class ElevatorState {
<<interface>>
+addRequest(Elevator, Request) void
+move(Elevator) void
+getDirection() Direction
}
class IdleState {
+addRequest(Elevator, Request) void
+move(Elevator) void
+getDirection() Direction
}
class MovingUpState {
+addRequest(Elevator, Request) void
+move(Elevator) void
+getDirection() Direction
}
class MovingDownState {
+addRequest(Elevator, Request) void
+move(Elevator) void
+getDirection() Direction
}
class Request {
-int targetFloor
-RequestSource source
-Direction direction
+getTargetFloor() int
+getDirection() Direction
}
class ElevatorObserver {
<<interface>>
+update(Elevator) void
}
class Display {
+update(Elevator) void
}
class ElevatorSelectionStrategy {
<<interface>>
+selectElevator(List~Elevator~, Request) Optional~Elevator~
}
class NearestElevatorStrategy {
+selectElevator(List~Elevator~, Request) Optional~Elevator~
}
class RequestSource {
<<enumeration>>
INTERNAL
EXTERNAL
}
class Direction {
<<enumeration>>
UP
DOWN
IDLE
}
ElevatorSystem "1" *-- "many" Elevator : contains
ElevatorSystem --> ElevatorSelectionStrategy : uses
Elevator --> ElevatorState : has-a
Elevator --> Request : processes
Elevator --> ElevatorObserver : notifies
ElevatorState <|.. IdleState
ElevatorState <|.. MovingUpState
ElevatorState <|.. MovingDownState
ElevatorObserver <|.. Display
ElevatorSelectionStrategy <|.. NearestElevatorStrategy
Request --> RequestSource : has
Request --> Direction : has
1. State Pattern - ElevatorState hierarchy
- Encapsulates behavior for each state (idle, moving up, moving down)
- Eliminates complex if-else chains
- Easy to add new states (e.g., EmergencyState)
2. Observer Pattern - ElevatorObserver
- Decouples elevator from display/notification systems
- Multiple observers can track elevator state
- Easy to add new observers (audio, logging, mobile app)
3. Strategy Pattern - ElevatorSelectionStrategy
- Encapsulates elevator selection algorithms
- Allows runtime swapping of strategies
- Easy to add new selection strategies
4. Singleton Pattern - ElevatorSystem
- Single system instance for entire building
- Ensures consistent state across all elevators
Step 6: Define Contracts & APIs
Section titled “Step 6: Define Contracts & APIs”Contracts define how classes interact - method signatures, interfaces, and behavior.
ElevatorState Interface:
1interface ElevatorState:2 """3 Defines behavior for elevator states.4 Each state implements different logic for movement and request handling.5 """6 def add_request(elevator: Elevator, request: Request) -> None:7 """8 Add a request to elevator based on current state.9
10 Args:11 elevator: The elevator receiving the request12 request: The floor request to add13
14 Behavior:15 - IdleState: Determines direction and transitions to MovingUp/MovingDown16 - MovingUpState: Adds to upRequests if above current floor, else downRequests17 - MovingDownState: Adds to downRequests if below current floor, else upRequests18 """19 pass20
21 def move(elevator: Elevator) -> None:22 """23 Move elevator based on current state.24
25 Args:26 elevator: The elevator to move27
28 Behavior:29 - IdleState: No movement30 - MovingUpState: Moves up, processes upRequests31 - MovingDownState: Moves down, processes downRequests32 """33 pass34
35 def get_direction() -> Direction:36 """37 Get the direction of this state.38
39 Returns:40 Direction: UP, DOWN, or IDLE41 """42 passElevatorObserver Interface:
1interface ElevatorObserver:2 """3 Observer interface for elevator state changes.4 Implementations can display, log, or notify about elevator updates.5 """6 def update(elevator: Elevator) -> None:7 """8 Called when elevator state changes.9
10 Args:11 elevator: The elevator that changed state12
13 Use Cases:14 - Display: Update floor/direction display15 - Logger: Log elevator movements16 - Notification: Send alerts to mobile app17 """18 passElevatorSelectionStrategy Interface:
1interface ElevatorSelectionStrategy:2 """3 Strategy for selecting which elevator should handle a request.4 Different implementations can use different algorithms.5 """6 def select_elevator(elevators: List[Elevator], request: Request) -> Optional[Elevator]:7 """8 Select the best elevator for a given request.9
10 Args:11 elevators: List of available elevators12 request: The floor request to handle13
14 Returns:15 Optional[Elevator]: Best elevator if found, None otherwise16
17 Considerations:18 - Current direction of elevator19 - Distance from request floor20 - Current load/capacity21 - Request queue length22 """23 passElevatorState Interface:
1interface ElevatorState {2 /**3 * Add a request to elevator based on current state.4 * @param elevator The elevator receiving the request5 * @param request The floor request to add6 */7 void addRequest(Elevator elevator, Request request);8
9 /**10 * Move elevator based on current state.11 * @param elevator The elevator to move12 */13 void move(Elevator elevator);14
15 /**16 * Get the direction of this state.17 * @return UP, DOWN, or IDLE18 */19 Direction getDirection();20}ElevatorObserver Interface:
1interface ElevatorObserver {2 /**3 * Called when elevator state changes.4 * @param elevator The elevator that changed state5 */6 void update(Elevator elevator);7}ElevatorSelectionStrategy Interface:
1interface ElevatorSelectionStrategy {2 /**3 * Select the best elevator for a given request.4 * @param elevators List of available elevators5 * @param request The floor request to handle6 * @return Best elevator if found, empty Optional otherwise7 */8 Optional<Elevator> selectElevator(List<Elevator> elevators, Request request);9}Core Class Contracts
Section titled “Core Class Contracts”ElevatorSystem:
1class ElevatorSystem:2 def get_instance() -> ElevatorSystem:3 """4 Get singleton instance of elevator system.5
6 Returns:7 ElevatorSystem: The single instance8 """9 pass10
11 def add_elevator(elevator: Elevator) -> None:12 """13 Add an elevator to the system.14
15 Args:16 elevator: Elevator to add17
18 Note:19 Starts elevator thread automatically20 """21 pass22
23 def request_elevator(floor: int, direction: Direction) -> None:24 """25 Request an elevator from a floor.26
27 Args:28 floor: Floor number requesting elevator29 direction: Direction passenger wants to go (UP/DOWN)30
31 Behavior:32 - Creates external request33 - Uses selection strategy to choose elevator34 - Adds request to selected elevator35 """36 pass37
38 def set_selection_strategy(strategy: ElevatorSelectionStrategy) -> None:39 """Set the elevator selection strategy."""40 passElevator:
1class Elevator:2 def add_request(request: Request) -> None:3 """4 Add a request to this elevator.5
6 Args:7 request: Floor request to add8
9 Behavior:10 - Delegates to current state11 - State determines how to handle request12 - Notifies observers after adding13 """14 pass15
16 def move() -> None:17 """18 Move elevator one floor based on current state.19
20 Behavior:21 - Delegates to current state22 - State controls movement direction23 - Stops at requested floors24 - Transitions states when appropriate25 - Notifies observers after moving26 """27 pass28
29 def add_observer(observer: ElevatorObserver) -> None:30 """Add an observer to be notified of state changes."""31 pass32
33 def notify_observers() -> None:34 """Notify all observers of current state."""35 passElevatorSystem:
1class ElevatorSystem {2 /**3 * Get singleton instance of elevator system.4 * @return The single instance5 */6 public static ElevatorSystem getInstance();7
8 /**9 * Add an elevator to the system.10 * @param elevator Elevator to add11 */12 public void addElevator(Elevator elevator);13
14 /**15 * Request an elevator from a floor.16 * @param floor Floor number requesting elevator17 * @param direction Direction passenger wants to go (UP/DOWN)18 */19 public void requestElevator(int floor, Direction direction);20
21 /** Set the elevator selection strategy. */22 public void setSelectionStrategy(ElevatorSelectionStrategy strategy);23}Elevator:
1class Elevator {2 /**3 * Add a request to this elevator.4 * @param request Floor request to add5 */6 public void addRequest(Request request);7
8 /**9 * Move elevator one floor based on current state.10 */11 public void move();12
13 /** Add an observer to be notified of state changes. */14 public void addObserver(ElevatorObserver observer);15
16 /** Notify all observers of current state. */17 public void notifyObservers();18}Contract Visualization
Section titled “Contract Visualization”Step 7: Handle Edge Cases
Section titled “Step 7: Handle Edge Cases”Edge cases are scenarios that might not be obvious but are important to handle.
Critical Edge Cases
Section titled “Critical Edge Cases”1. All Elevators Busy
- Queue request or assign to least busy elevator
- Provide feedback to user
- Consider wait time estimation
2. Elevator at Capacity
- Reject new passengers when full
- Don’t stop at floors if full (unless requested floor)
- Track current load vs max capacity
3. Request While Moving
- Add request to appropriate queue (up/down)
- Process requests in direction of travel first
- Handle direction changes when queue empties
4. Multiple Equidistant Elevators
- Use tie-breaker (least loaded, first available)
- Consider current direction preference
- Load balancing across elevators
5. Concurrent Requests
- Thread-safe request handling
- Atomic operations for state changes
- Prevent race conditions in selection
6. Invalid Floor Requests
- Validate floor exists
- Reject requests for current floor
- Handle edge cases (floor 0, negative floors)
Edge Case Handling Flow
Section titled “Edge Case Handling Flow”Thread Safety Considerations
Section titled “Thread Safety Considerations”1# Thread-safe elevator operations2class Elevator:3 def __init__(self, id: int):4 self.id = id5 self.current_floor = AtomicInteger(0)6 self.up_requests = ConcurrentSkipListSet() # Thread-safe sorted set7 self.down_requests = ConcurrentSkipListSet()8 self.observers = CopyOnWriteArrayList() # Thread-safe list9 self._lock = ReentrantLock()10
11 def add_request(self, request: Request) -> None:12 with self._lock: # Critical section13 self.state.add_request(self, request)14 self.notify_observers()15
16 def move(self) -> None:17 with self._lock: # Critical section18 self.state.move(self)19 self.notify_observers()1// Thread-safe elevator operations2class Elevator {3 private int id;4 private AtomicInteger currentFloor;5 private TreeSet<Integer> upRequests;6 private TreeSet<Integer> downRequests;7 private List<ElevatorObserver> observers;8 private final ReentrantLock lock = new ReentrantLock();9
10 public Elevator(int id) {11 this.id = id;12 this.currentFloor = new AtomicInteger(0);13 // Note: In real Java, use ConcurrentSkipListSet or synchronize access14 this.upRequests = new TreeSet<>();15 this.downRequests = new TreeSet<>();16 this.observers = new CopyOnWriteArrayList<>();17 }18
19 public void addRequest(Request request) {20 lock.lock(); // Critical section21 try {22 state.addRequest(this, request);23 notifyObservers();24 } finally {25 lock.unlock();26 }27 }28
29 public void move() {30 lock.lock(); // Critical section31 try {32 state.move(this);33 notifyObservers();34 } finally {35 lock.unlock();36 }37 }38}Step 8: Code Implementation
Section titled “Step 8: Code Implementation”Finally, implement your design following SOLID principles and design patterns.
Complete Implementation
Section titled “Complete Implementation”1from abc import ABC, abstractmethod2from enum import Enum3from typing import Optional, List, Dict, Set4import threading5from collections import deque6import time7
8# =====================9# ENUMERATIONS10# =====================11
12class RequestSource(Enum):13 INTERNAL = "INTERNAL" # Request from inside elevator14 EXTERNAL = "EXTERNAL" # Request from floor button15
16class Direction(Enum):17 UP = "UP"18 DOWN = "DOWN"19 IDLE = "IDLE"20
21# =====================22# REQUEST23# =====================24
25class Request:26 """Represents a floor request."""27
28 def __init__(self, target_floor: int, source: RequestSource, direction: Direction):29 self.target_floor = target_floor30 self.source = source31 self.direction = direction32
33 def get_target_floor(self) -> int:34 return self.target_floor35
36 def get_direction(self) -> Direction:37 return self.direction38
39# =====================40# OBSERVER PATTERN41# =====================42
43class ElevatorObserver(ABC):44 """Observer interface for elevator state changes."""45
46 @abstractmethod47 def update(self, elevator: 'Elevator') -> None:48 pass49
50class Display(ElevatorObserver):51 """Display that shows elevator status."""52
53 def update(self, elevator: 'Elevator') -> None:54 print(f"Display: Elevator {elevator.get_id()} at Floor {elevator.get_current_floor()} "55 f"Direction: {elevator.get_direction().value}")56
57# =====================58# STATE PATTERN59# =====================60
61class ElevatorState(ABC):62 """Abstract state interface."""63
64 @abstractmethod65 def add_request(self, elevator: 'Elevator', request: Request) -> None:66 pass67
68 @abstractmethod69 def move(self, elevator: 'Elevator') -> None:70 pass71
72 @abstractmethod73 def get_direction(self) -> Direction:74 pass75
76class IdleState(ElevatorState):77 """Elevator is idle, waiting for requests."""78
79 def add_request(self, elevator: 'Elevator', request: Request) -> None:80 target = request.get_target_floor()81 current = elevator.get_current_floor()82
83 if target > current:84 elevator.set_state(MovingUpState())85 elevator.get_up_requests().add(target)86 elif target < current:87 elevator.set_state(MovingDownState())88 elevator.get_down_requests().add(target)89 # If target == current, do nothing (already there)90
91 def move(self, elevator: 'Elevator') -> None:92 # Stay idle, no movement93 pass94
95 def get_direction(self) -> Direction:96 return Direction.IDLE97
98class MovingUpState(ElevatorState):99 """Elevator is moving up."""100
101 def add_request(self, elevator: 'Elevator', request: Request) -> None:102 target = request.get_target_floor()103 current = elevator.get_current_floor()104
105 if target > current:106 # Add to up requests (will be processed while going up)107 elevator.get_up_requests().add(target)108 else:109 # Add to down requests (will be processed after up requests)110 elevator.get_down_requests().add(target)111
112 def move(self, elevator: 'Elevator') -> None:113 up_requests = elevator.get_up_requests()114 down_requests = elevator.get_down_requests()115 current = elevator.get_current_floor()116
117 if not up_requests:118 # No more up requests, check down requests119 if down_requests:120 elevator.set_state(MovingDownState())121 else:122 elevator.set_state(IdleState())123 return124
125 # Get next floor to visit126 next_floor = min(up_requests)127
128 if current < next_floor:129 # Move up one floor130 elevator.set_current_floor(current + 1)131
132 # Check if we've reached a requested floor133 if elevator.get_current_floor() in up_requests:134 up_requests.remove(elevator.get_current_floor())135 print(f"Elevator {elevator.get_id()} stopped at floor {elevator.get_current_floor()}")136 # In real system: open doors, wait, close doors137
138 def get_direction(self) -> Direction:139 return Direction.UP140
141class MovingDownState(ElevatorState):142 """Elevator is moving down."""143
144 def add_request(self, elevator: 'Elevator', request: Request) -> None:145 target = request.get_target_floor()146 current = elevator.get_current_floor()147
148 if target < current:149 # Add to down requests (will be processed while going down)150 elevator.get_down_requests().add(target)151 else:152 # Add to up requests (will be processed after down requests)153 elevator.get_up_requests().add(target)154
155 def move(self, elevator: 'Elevator') -> None:156 up_requests = elevator.get_up_requests()157 down_requests = elevator.get_down_requests()158 current = elevator.get_current_floor()159
160 if not down_requests:161 # No more down requests, check up requests162 if up_requests:163 elevator.set_state(MovingUpState())164 else:165 elevator.set_state(IdleState())166 return167
168 # Get next floor to visit169 next_floor = max(down_requests)170
171 if current > next_floor:172 # Move down one floor173 elevator.set_current_floor(current - 1)174
175 # Check if we've reached a requested floor176 if elevator.get_current_floor() in down_requests:177 down_requests.remove(elevator.get_current_floor())178 print(f"Elevator {elevator.get_id()} stopped at floor {elevator.get_current_floor()}")179 # In real system: open doors, wait, close doors180
181 def get_direction(self) -> Direction:182 return Direction.DOWN183
184# =====================185# ELEVATOR186# =====================187
188class Elevator(threading.Thread):189 """Represents an elevator car."""190
191 def __init__(self, elevator_id: int):192 super().__init__(daemon=True)193 self.elevator_id = elevator_id194 self.current_floor = 0195 self.state: ElevatorState = IdleState()196 self.up_requests: Set[int] = set()197 self.down_requests: Set[int] = set()198 self.observers: List[ElevatorObserver] = []199 self.is_running = True200 self._lock = threading.Lock()201
202 def add_observer(self, observer: ElevatorObserver) -> None:203 """Add an observer to be notified of state changes."""204 with self._lock:205 self.observers.append(observer)206
207 def notify_observers(self) -> None:208 """Notify all observers of state change."""209 with self._lock:210 for observer in self.observers:211 observer.update(self)212
213 def add_request(self, request: Request) -> None:214 """Add a request to this elevator."""215 with self._lock:216 self.state.add_request(self, request)217 self.notify_observers()218
219 def move(self) -> None:220 """Move elevator based on current state."""221 with self._lock:222 self.state.move(self)223 self.notify_observers()224
225 def run(self) -> None:226 """Elevator thread main loop."""227 while self.is_running:228 self.move()229 time.sleep(1) # Simulate movement time230
231 def stop(self) -> None:232 """Stop the elevator thread."""233 self.is_running = False234
235 # Getters and setters236 def get_id(self) -> int:237 return self.elevator_id238
239 def get_current_floor(self) -> int:240 return self.current_floor241
242 def set_current_floor(self, floor: int) -> None:243 self.current_floor = floor244
245 def set_state(self, state: ElevatorState) -> None:246 self.state = state247
248 def get_up_requests(self) -> Set[int]:249 return self.up_requests250
251 def get_down_requests(self) -> Set[int]:252 return self.down_requests253
254 def get_direction(self) -> Direction:255 return self.state.get_direction()256
257# =====================258# STRATEGY PATTERN259# =====================260
261class ElevatorSelectionStrategy(ABC):262 """Strategy for selecting which elevator handles a request."""263
264 @abstractmethod265 def select_elevator(self, elevators: List[Elevator], request: Request) -> Optional[Elevator]:266 pass267
268class NearestElevatorStrategy(ElevatorSelectionStrategy):269 """Selects the nearest available elevator."""270
271 def select_elevator(self, elevators: List[Elevator], request: Request) -> Optional[Elevator]:272 if not elevators:273 return None274
275 target_floor = request.get_target_floor()276 best_elevator = None277 min_distance = float('inf')278
279 for elevator in elevators:280 distance = abs(elevator.get_current_floor() - target_floor)281
282 # Prefer elevators moving towards request or idle283 direction = elevator.get_direction()284 if direction == request.get_direction() or direction == Direction.IDLE:285 if distance < min_distance:286 min_distance = distance287 best_elevator = elevator288
289 # If no elevator moving in right direction, pick nearest290 if best_elevator is None:291 for elevator in elevators:292 distance = abs(elevator.get_current_floor() - target_floor)293 if distance < min_distance:294 min_distance = distance295 best_elevator = elevator296
297 return best_elevator298
299# =====================300# SINGLETON PATTERN - MAIN SYSTEM301# =====================302
303class ElevatorSystem:304 """Main elevator system (Singleton)."""305
306 _instance: Optional['ElevatorSystem'] = None307 _lock = threading.Lock()308
309 def __new__(cls):310 if cls._instance is None:311 with cls._lock:312 if cls._instance is None:313 cls._instance = super().__new__(cls)314 return cls._instance315
316 def __init__(self):317 # Prevent re-initialization318 if hasattr(self, '_initialized'):319 return320
321 self.elevators: Dict[int, Elevator] = {}322 self.selection_strategy: ElevatorSelectionStrategy = NearestElevatorStrategy()323 self._initialized = True324
325 def add_elevator(self, elevator: Elevator) -> None:326 """Add an elevator to the system and start its thread."""327 self.elevators[elevator.get_id()] = elevator328 elevator.start()329
330 def set_selection_strategy(self, strategy: ElevatorSelectionStrategy) -> None:331 """Set the elevator selection strategy."""332 self.selection_strategy = strategy333
334 def request_elevator(self, floor: int, direction: Direction) -> None:335 """336 Request an elevator from a floor.337
338 Args:339 floor: Floor number requesting elevator340 direction: Direction passenger wants to go341 """342 request = Request(floor, RequestSource.EXTERNAL, direction)343 elevator = self.selection_strategy.select_elevator(344 list(self.elevators.values()),345 request346 )347
348 if elevator:349 elevator.add_request(request)350 print(f"Request from floor {floor} going {direction.value} assigned to Elevator {elevator.get_id()}")351 else:352 print(f"No elevator available for request from floor {floor}")353
354# =====================355# DEMO356# =====================357
358if __name__ == "__main__":359 # Get singleton instance360 system = ElevatorSystem.getInstance()361
362 # Create elevators363 elevator1 = Elevator(1)364 elevator1.add_observer(Display())365 system.add_elevator(elevator1)366
367 elevator2 = Elevator(2)368 elevator2.add_observer(Display())369 system.add_elevator(elevator2)370
371 # Request elevators372 system.request_elevator(5, Direction.UP)373 system.request_elevator(3, Direction.DOWN)374
375 # Simulate time376 time.sleep(10)377
378 # Stop elevators379 elevator1.stop()380 elevator2.stop()1import java.util.*;2import java.util.concurrent.*;3import java.util.concurrent.atomic.AtomicInteger;4
5// =====================6// ENUMERATIONS7// =====================8enum RequestSource {9 INTERNAL, EXTERNAL10}11
12enum Direction {13 UP, DOWN, IDLE14}15
16// =====================17// REQUEST18// =====================19class Request {20 private int targetFloor;21 private RequestSource source;22 private Direction direction;23
24 public Request(int targetFloor, RequestSource source, Direction direction) {25 this.targetFloor = targetFloor;26 this.source = source;27 this.direction = direction;28 }29
30 public int getTargetFloor() { return targetFloor; }31 public Direction getDirection() { return direction; }32}33
34// =====================35// OBSERVER PATTERN36// =====================37interface ElevatorObserver {38 void update(Elevator elevator);39}40
41class Display implements ElevatorObserver {42 @Override43 public void update(Elevator elevator) {44 System.out.println("Display: Elevator " + elevator.getId() +45 " at Floor " + elevator.getCurrentFloor() +46 " Direction: " + elevator.getDirection());47 }48}49
50// =====================51// STATE PATTERN52// =====================53interface ElevatorState {54 void addRequest(Elevator elevator, Request request);55 void move(Elevator elevator);56 Direction getDirection();57}58
59class IdleState implements ElevatorState {60 @Override61 public void addRequest(Elevator elevator, Request request) {62 int target = request.getTargetFloor();63 int current = elevator.getCurrentFloor();64
65 if (target > current) {66 elevator.setState(new MovingUpState());67 elevator.getUpRequests().add(target);68 } else if (target < current) {69 elevator.setState(new MovingDownState());70 elevator.getDownRequests().add(target);71 }72 }73
74 @Override75 public void move(Elevator elevator) {76 // Stay idle77 }78
79 @Override80 public Direction getDirection() {81 return Direction.IDLE;82 }83}84
85class MovingUpState implements ElevatorState {86 @Override87 public void addRequest(Elevator elevator, Request request) {88 int target = request.getTargetFloor();89 int current = elevator.getCurrentFloor();90
91 if (target > current) {92 elevator.getUpRequests().add(target);93 } else {94 elevator.getDownRequests().add(target);95 }96 }97
98 @Override99 public void move(Elevator elevator) {100 TreeSet<Integer> upRequests = elevator.getUpRequests();101 TreeSet<Integer> downRequests = elevator.getDownRequests();102
103 if (upRequests.isEmpty()) {104 if (!downRequests.isEmpty()) {105 elevator.setState(new MovingDownState());106 } else {107 elevator.setState(new IdleState());108 }109 return;110 }111
112 int nextFloor = upRequests.first();113 int current = elevator.getCurrentFloor();114
115 if (current < nextFloor) {116 elevator.setCurrentFloor(current + 1);117 }118
119 if (elevator.getCurrentFloor() == nextFloor) {120 upRequests.remove(nextFloor);121 System.out.println("Elevator " + elevator.getId() +122 " stopped at floor " + nextFloor);123 }124 }125
126 @Override127 public Direction getDirection() {128 return Direction.UP;129 }130}131
132class MovingDownState implements ElevatorState {133 @Override134 public void addRequest(Elevator elevator, Request request) {135 int target = request.getTargetFloor();136 int current = elevator.getCurrentFloor();137
138 if (target < current) {139 elevator.getDownRequests().add(target);140 } else {141 elevator.getUpRequests().add(target);142 }143 }144
145 @Override146 public void move(Elevator elevator) {147 TreeSet<Integer> upRequests = elevator.getUpRequests();148 TreeSet<Integer> downRequests = elevator.getDownRequests();149
150 if (downRequests.isEmpty()) {151 if (!upRequests.isEmpty()) {152 elevator.setState(new MovingUpState());153 } else {154 elevator.setState(new IdleState());155 }156 return;157 }158
159 int nextFloor = downRequests.first();160 int current = elevator.getCurrentFloor();161
162 if (current > nextFloor) {163 elevator.setCurrentFloor(current - 1);164 }165
166 if (elevator.getCurrentFloor() == nextFloor) {167 downRequests.remove(nextFloor);168 System.out.println("Elevator " + elevator.getId() +169 " stopped at floor " + nextFloor);170 }171 }172
173 @Override174 public Direction getDirection() {175 return Direction.DOWN;176 }177}178
179// =====================180// ELEVATOR181// =====================182class Elevator implements Runnable {183 private int id;184 private AtomicInteger currentFloor;185 private ElevatorState state;186 private TreeSet<Integer> upRequests;187 private TreeSet<Integer> downRequests;188 private List<ElevatorObserver> observers;189 private boolean isRunning;190 private final ReentrantLock lock = new ReentrantLock();191
192 public Elevator(int id) {193 this.id = id;194 this.currentFloor = new AtomicInteger(0);195 this.state = new IdleState();196 this.upRequests = new TreeSet<>();197 this.downRequests = new TreeSet<>(Collections.reverseOrder());198 this.observers = new CopyOnWriteArrayList<>();199 this.isRunning = true;200 }201
202 public void addObserver(ElevatorObserver observer) {203 observers.add(observer);204 }205
206 public void notifyObservers() {207 for (ElevatorObserver observer : observers) {208 observer.update(this);209 }210 }211
212 public void addRequest(Request request) {213 lock.lock();214 try {215 state.addRequest(this, request);216 notifyObservers();217 } finally {218 lock.unlock();219 }220 }221
222 public void move() {223 lock.lock();224 try {225 state.move(this);226 notifyObservers();227 } finally {228 lock.unlock();229 }230 }231
232 @Override233 public void run() {234 while (isRunning) {235 move();236 try {237 Thread.sleep(1000);238 } catch (InterruptedException e) {239 Thread.currentThread().interrupt();240 break;241 }242 }243 }244
245 public void stop() {246 isRunning = false;247 }248
249 // Getters and setters250 public int getId() { return id; }251 public int getCurrentFloor() { return currentFloor.get(); }252 public void setCurrentFloor(int floor) { currentFloor.set(floor); }253 public void setState(ElevatorState state) { this.state = state; }254 public TreeSet<Integer> getUpRequests() { return upRequests; }255 public TreeSet<Integer> getDownRequests() { return downRequests; }256 public Direction getDirection() { return state.getDirection(); }257}258
259// =====================260// STRATEGY PATTERN261// =====================262interface ElevatorSelectionStrategy {263 Optional<Elevator> selectElevator(List<Elevator> elevators, Request request);264}265
266class NearestElevatorStrategy implements ElevatorSelectionStrategy {267 @Override268 public Optional<Elevator> selectElevator(List<Elevator> elevators, Request request) {269 if (elevators.isEmpty()) {270 return Optional.empty();271 }272
273 int targetFloor = request.getTargetFloor();274 Elevator bestElevator = null;275 int minDistance = Integer.MAX_VALUE;276
277 for (Elevator elevator : elevators) {278 int distance = Math.abs(elevator.getCurrentFloor() - targetFloor);279 Direction dir = elevator.getDirection();280
281 if (dir == request.getDirection() || dir == Direction.IDLE) {282 if (distance < minDistance) {283 minDistance = distance;284 bestElevator = elevator;285 }286 }287 }288
289 if (bestElevator == null) {290 for (Elevator elevator : elevators) {291 int distance = Math.abs(elevator.getCurrentFloor() - targetFloor);292 if (distance < minDistance) {293 minDistance = distance;294 bestElevator = elevator;295 }296 }297 }298
299 return Optional.ofNullable(bestElevator);300 }301}302
303// =====================304// SINGLETON PATTERN - MAIN SYSTEM305// =====================306class ElevatorSystem {307 private static volatile ElevatorSystem instance;308 private static final Object lock = new Object();309
310 private Map<Integer, Elevator> elevators;311 private ElevatorSelectionStrategy selectionStrategy;312 private ExecutorService executorService;313
314 private ElevatorSystem() {315 this.elevators = new ConcurrentHashMap<>();316 this.selectionStrategy = new NearestElevatorStrategy();317 this.executorService = Executors.newCachedThreadPool();318 }319
320 public static ElevatorSystem getInstance() {321 if (instance == null) {322 synchronized (lock) {323 if (instance == null) {324 instance = new ElevatorSystem();325 }326 }327 }328 return instance;329 }330
331 public void addElevator(Elevator elevator) {332 elevators.put(elevator.getId(), elevator);333 executorService.submit(elevator);334 }335
336 public void setSelectionStrategy(ElevatorSelectionStrategy strategy) {337 this.selectionStrategy = strategy;338 }339
340 public void requestElevator(int floor, Direction direction) {341 Request request = new Request(floor, RequestSource.EXTERNAL, direction);342 Optional<Elevator> elevatorOpt = selectionStrategy.selectElevator(343 new ArrayList<>(elevators.values()),344 request345 );346
347 elevatorOpt.ifPresentOrElse(348 elevator -> {349 elevator.addRequest(request);350 System.out.println("Request from floor " + floor +351 " going " + direction +352 " assigned to Elevator " + elevator.getId());353 },354 () -> System.out.println("No elevator available for request from floor " + floor)355 );356 }357}358
359// =====================360// DEMO361// =====================362public class ElevatorSystemDemo {363 public static void main(String[] args) {364 ElevatorSystem system = ElevatorSystem.getInstance();365
366 Elevator e1 = new Elevator(1);367 e1.addObserver(new Display());368 system.addElevator(e1);369
370 Elevator e2 = new Elevator(2);371 e2.addObserver(new Display());372 system.addElevator(e2);373
374 system.requestElevator(5, Direction.UP);375 system.requestElevator(3, Direction.DOWN);376
377 try {378 Thread.sleep(10000);379 } catch (InterruptedException e) {380 e.printStackTrace();381 }382
383 e1.stop();384 e2.stop();385 }386}Key Implementation Highlights
Section titled “Key Implementation Highlights”1. State Pattern:
IdleState,MovingUpState,MovingDownStateencapsulate behavior- Eliminates complex if-else chains
- Easy to add new states (e.g.,
EmergencyState)
2. Observer Pattern:
Displayobserves elevator state changes- Easy to add more observers (audio, logging, mobile app)
- Decouples elevator from presentation
3. Strategy Pattern:
NearestElevatorStrategyselects best elevator- Easy to swap strategies (e.g.,
LeastLoadedStrategy)
4. Thread Safety:
- Each elevator runs in separate thread
- Locks for critical sections
- Thread-safe collections
5. Request Management:
TreeSetfor sorted request queues- Separate queues for up and down directions
- Efficient floor processing
Problem: Without State Pattern, Elevator.move() would look like:
1def move(self):2 if self.direction == UP:3 # 50 lines of up logic4 if self.current_floor < self.next_floor:5 self.current_floor += 16 # ... more up logic7 elif self.direction == DOWN:8 # 50 lines of down logic9 # ... more down logic10 else: # IDLE11 # idle logic1public void move() {2 if (direction == UP) {3 // 50 lines of up logic4 if (currentFloor < nextFloor) {5 currentFloor++;6 }7 // ... more up logic8 } else if (direction == DOWN) {9 // 50 lines of down logic10 // ... more down logic11 } else { // IDLE12 // idle logic13 }14}Why State Pattern?
- Eliminates if-else chains - Each state encapsulates its behavior
- Open/Closed Principle - Add new states without modifying
Elevator - Single Responsibility - Each state class handles one behavior
- Easier to test - Test each state independently
How it works:
Elevatordelegatesmove()andaddRequest()to currentElevatorState- States can transition:
IdleState→MovingUpState→MovingDownState→IdleState - State decides how to handle requests based on current direction
Problem: If Elevator directly updates Display, what happens when we add:
- Audio announcements?
- Logging system?
- Mobile app notifications?
We’d have to modify Elevator class each time → Violates Open/Closed Principle
Why Observer Pattern?
- Loose Coupling -
Elevatordoesn’t know about concrete observers - Extensibility - Add new observers without changing
Elevator - Separation of Concerns - Elevator focuses on movement, observers handle presentation
How it works:
Elevatormaintains list ofElevatorObserverreferences- When state changes, calls
notifyObservers() - Each observer’s
update()method is called with elevator reference - Observer pulls needed data (floor, direction) from elevator
Problem: How should ElevatorSystem choose which elevator to assign?
- Nearest elevator?
- Least loaded?
- Zone-based (elevator 1 serves floors 1-10, elevator 2 serves 11-20)?
- SCAN algorithm?
Why Strategy Pattern?
- Algorithm Encapsulation - Each selection algorithm in its own class
- Runtime Flexibility - Can switch strategies without code changes
- Testability - Test each strategy independently
- Future-proof - Easy to add new strategies (e.g.,
EnergyEfficientStrategy)
How it works:
ElevatorSystemholds reference toElevatorSelectionStrategyrequestElevator()delegates tostrategy.selectElevator()- Strategy analyzes all elevators and request, returns best match
- Can be swapped:
system.setSelectionStrategy(new LeastLoadedStrategy())
System Flow Diagrams
Section titled “System Flow Diagrams”Request Handling Flow
Section titled “Request Handling Flow”Elevator Movement Flow
Section titled “Elevator Movement Flow”Extensibility & Future Enhancements
Section titled “Extensibility & Future Enhancements”Easy to Extend
Section titled “Easy to Extend”1. Add New State:
1class EmergencyState(ElevatorState):2 """Elevator in emergency mode - stops immediately."""3
4 def add_request(self, elevator, request):5 # Ignore all requests during emergency6 pass7
8 def move(self, elevator):9 # Stop at nearest floor and open doors10 elevator.stop_at_floor()11 elevator.open_doors()12
13 def get_direction(self):14 return Direction.IDLE15
16# Use it17elevator.set_state(EmergencyState())1class EmergencyState implements ElevatorState {2 /** Elevator in emergency mode - stops immediately. */3
4 @Override5 public void addRequest(Elevator elevator, Request request) {6 // Ignore all requests during emergency7 }8
9 @Override10 public void move(Elevator elevator) {11 // Stop at nearest floor and open doors12 elevator.stopAtFloor();13 elevator.openDoors();14 }15
16 @Override17 public Direction getDirection() {18 return Direction.IDLE;19 }20}21
22// Use it23elevator.setState(new EmergencyState());2. Add New Observer:
1class AudioAnnouncement(ElevatorObserver):2 """Announces floor numbers audibly."""3
4 def update(self, elevator):5 print(f"Floor {elevator.get_current_floor()}")6 # In real system: play audio file7
8# Use it9elevator.add_observer(AudioAnnouncement())1class AudioAnnouncement implements ElevatorObserver {2 /** Announces floor numbers audibly. */3
4 @Override5 public void update(Elevator elevator) {6 System.out.println("Floor " + elevator.getCurrentFloor());7 // In real system: play audio file8 }9}10
11// Use it12elevator.addObserver(new AudioAnnouncement());3. Add New Selection Strategy:
1class LeastLoadedStrategy(ElevatorSelectionStrategy):2 """Selects elevator with fewest pending requests."""3
4 def select_elevator(self, elevators, request):5 return min(elevators,6 key=lambda e: len(e.get_up_requests()) + len(e.get_down_requests()))7
8# Use it9system.set_selection_strategy(LeastLoadedStrategy())1class LeastLoadedStrategy implements ElevatorSelectionStrategy {2 /** Selects elevator with fewest pending requests. */3
4 @Override5 public Optional<Elevator> selectElevator(List<Elevator> elevators, Request request) {6 return elevators.stream()7 .min(Comparator.comparingInt(e ->8 e.getUpRequests().size() + e.getDownRequests().size()));9 }10}11
12// Use it13system.setSelectionStrategy(new LeastLoadedStrategy());Future Enhancements
Section titled “Future Enhancements”1. Capacity Management:
- Track current load vs max capacity
- Reject passengers when full
- Don’t stop at floors if full (unless requested floor)
2. Zone-based Strategy:
- Elevators serve specific floor ranges
ZoneBasedStrategyassigns requests to appropriate zone
3. SCAN Algorithm:
- Elevator goes to top, then bottom, then repeats
- Efficient for high-traffic scenarios
4. Request Prioritization:
- VIP floors get priority
- Emergency requests override normal requests
- Express mode (skip floors)
5. Maintenance Mode:
- Elevator out of service
MaintenanceStatethat ignores requests- Admin can enable/disable elevators
Summary
Section titled “Summary”Key Takeaways
Section titled “Key Takeaways”- Follow Systematic Approach - Don’t jump to code
- Identify Actors First - Who uses the system?
- Identify Entities - What are the core objects?
- Assign Responsibilities - Single Responsibility Principle
- Design Class Diagrams - Visualize structure and relationships
- Define Contracts - Clear interfaces and APIs
- Handle Edge Cases - Think about errors and concurrency
- Use Design Patterns - State, Observer, Strategy
- Make it Extensible - Easy to add new features
- State Pattern - Encapsulates behavior for each elevator state
- Observer Pattern - Decouples elevator from display/notifications
- Strategy Pattern - Swappable elevator selection algorithms
- Singleton Pattern - Single system instance
Best Practices Demonstrated
Section titled “Best Practices Demonstrated”- SOLID principles (SRP, OCP)
- Thread safety considerations
- Clear separation of concerns
- Extensible design
- Error handling
- Clean code structure
Next Steps
Section titled “Next Steps”Now that you’ve mastered the Elevator System:
Practice Similar Problems:
- Traffic Light System
- Vending Machine
- ATM System
- Coffee Machine
Explore More Patterns:
- Command Pattern (for request queuing)
- Chain of Responsibility (for request processing)
- Template Method (for movement algorithms)
Deepen Your Understanding:
- Read about concurrent state machines
- Study distributed elevator systems
- Learn about real-time scheduling algorithms