LLD for Elevator System: Designing a Smart Lift
The Hotel Lift Problem: What We Are Designing
A hotel has 10 floors and 3 elevators.
π― System Scenario & Requirements
System Goal
A hotel has 10 floors and 3 elevators.
Actors & Components
- Application or API tier
- Core service under design
- Operator or SRE observer
Functional Requirements
- A hotel has 10 floors and 3 elevators.
- At any moment: - External requests come in: "Someone on Floor 5 pressed UP." - Internal requests come in: "Someone inside Lift 1 pressed Floor 10." - The system must decide which car to dispatch, and which order to servβ¦
- This is a classic State Machine + Scheduling design.
Constraints & Assumptions
β‘ Execution Stages & Decision Trace
Stage 1 β Elevator System Basics: Entities, States, and Button Types
Before writing a single line of code it helps to name every moving part in plain English.
Core entities: States an ElevatorCar can be in: - IDLE β the car is stopped and has no pending stops.
Deep Dive
Scenario context
A hotel has 10 floors and 3 elevators. At any moment:
- External requests come in: "Someone on Floor 5 pressed UP."
- Internal requests come in: "Someone inside Lift 1 pressed Floor 10."
- The system must decide which car to dispatch, and which order to serve floors.
This is a classic State Machine + Scheduling design. The key insight is that a lift behaves differently based on its current state β and we shouldβ¦
This walkthrough focuses on The Hotel Lift Problem: What We Are Designing.
Before making a design choice, clarify:
- A hotel has 10 floors and 3 elevators.
- At any moment: - External requests come in: "Someone on Floor 5 pressed UP." - Internal requests come in: "Someone inside Lift 1 pressed Floor 10." - The system must decide which car to dispatch, and which order to servβ¦
- This is a classic State Machine + Scheduling design.
Concepts in play: low-level-design-systems, jvm-internals-and-garbage-collection, design-patterns-gof, design-principles-solid
Stage 1 β Elevator System Basics: Entities, States, and Button Types
- Before writing a single line of code it helps to name every moving part in plain English.
- Core entities: States an ElevatorCar can be in: - IDLE β the car is stopped and has no pending stops.
- MOVING UP β the car is travelling upward and serving stops along the way.
- MOVING DOWN β the car is travelling downward and serving stops along the way.
Observable outcome: - MOVING DOWN β the car is travelling downward and serving stops along the way.
Stage 2 β Domain Model: The Core Entities
- ElevatorCar: Holds a sorted set of destination floors.
- Has a state (IDLE, MOVING UP, MOVING DOWN).
- Assigns incoming Request to the best car.
- Request: Either ExternalRequest (floor + direction pressed outside) or InternalRequest (destination floor pressed inside the car).
classDiagram
class Building {
+List~Floor~ floors
+List~ElevatorCar~ cars
+Dispatcher dispatcher
+handleExternalRequest(Request r)
}
class ElevatorCar {
+int id
+int currentFloor
+Direction direction
+ElevatorState state
-TreeSet~Integer~ stops
+addStop(int floor)
+move()
+getCurrentFloor() int
}
class Dispatcher {
-DispatchStrategy strategy
+assign(Request r) ElevatorCar
}
class Request {
+int sourceFloor
+Direction direction
}
class ElevatorState {
<>
+handleExternalRequest(ElevatorCar car, Request r)
+handleInternalRequest(ElevatorCar car, int floor)
+isIdle() bool
}
class DispatchStrategy {
<>
+assign(Request request, List~ElevatorCar~ available) ElevatorCar
}
class IdleState {
+handleExternalRequest(ElevatorCar, Request)
+handleInternalRequest(ElevatorCar, int)
+isIdle() bool
}
class MovingUpState {
+handleExternalRequest(ElevatorCar, Request)
+handleInternalRequest(ElevatorCar, int)
+isIdle() bool
}
class MovingDownState {
+handleExternalRequest(ElevatorCar, Request)
+handleInternalRequest(ElevatorCar, int)
+isIdle() bool
}
class NearestCarStrategy {
+assign(Request, List~ElevatorCar~) ElevatorCar
}
Building "1" *-- "many" ElevatorCar : contains
Building --> Dispatcher : uses
Dispatcher o-- DispatchStrategy : uses (injected)
ElevatorCar o-- ElevatorState : state (State Pattern)
ElevatorState <|.. IdleState : implements
ElevatorState <|.. MovingUpState : implements
ElevatorState <|.. MovingDownState : implements
DispatchStrategy <|.. NearestCarStrategy : implements
Dispatcher --> ElevatorCar : assigns
Observable outcome: - Request: Either ExternalRequest (floor + direction pressed outside) or InternalRequest (destination floor pressed inside the car).
Stage 3 β Deep Dive: State Pattern for Elevator Movement
- An elevator's behavior depends on its current state.
- We make this explicit with the State pattern: The state transitions:
public interface ElevatorState {
void handleRequest(ElevatorCar car, int targetFloor);
String name();
}
public class IdleState implements ElevatorState {
public void handleRequest(ElevatorCar car, int targetFloor) {
car.addStop(targetFloor);
if (targetFloor > car.getCurrentFloor()) {
car.setState(new MovingUpState());
} else if (targetFloor < car.getCurrentFloor()) {
car.setState(new MovingDownState());
}
}
public String name() { return "IDLE"; }
}
public class MovingUpState implements ElevatorState {
public void handleRequest(ElevatorCar car, int targetFloor) {
// SCAN: only add if it's above current floor
if (targetFloor >= car.getCurrentFloor()) {
car.addStop(targetFloor); // serve on the way
} else {
car.deferRequest(targetFloor); // serve on the next sweep
}
}
public String name() { return "MOVING_UP"; }
}
stateDiagram-v2
[*] --> IDLE
IDLE --> MOVING_UP : request above current floor
IDLE --> MOVING_DOWN : request below current floor
MOVING_UP --> IDLE : no more up requests
MOVING_UP --> MOVING_DOWN : switch at top
MOVING_DOWN --> IDLE : no more down requests
MOVING_DOWN --> MOVING_UP : switch at bottom
Observable outcome: We make this explicit with the State pattern: The state transitions:
Stage 4 β Dispatcher: Strategy Pattern for Assignment Policy
- The Dispatcher uses the Strategy pattern so we can swap assignment algorithms: canService() checks the car's state: - IDLE β always serviceable.
- MOVING UP β serviceable if request is above current floor and direction matches.
- MOVING DOWN β serviceable if request is below current floor and direction matches.
public interface DispatchStrategy {
ElevatorCar select(List<ElevatorCar> cars, Request request);
}
// Nearest-Car strategy: pick the car that will reach the requester soonest
public class NearestCarStrategy implements DispatchStrategy {
public ElevatorCar select(List<ElevatorCar> cars, Request request) {
return cars.stream()
.filter(c -> c.canService(request))
.min(Comparator.comparingInt(c ->
Math.abs(c.getCurrentFloor() - request.getSourceFloor())))
.orElse(null);
}
}
Observable outcome: - MOVING DOWN β serviceable if request is below current floor and direction matches.
Stage 5 β Interface Contracts: DispatchStrategy and ElevatorState
- Two interfaces form the entire behavioral backbone of this design.
- Every concrete class β state implementations, dispatch algorithms, the Dispatcher itself β is wired together through these two contracts.
- Why separate handleExternalRequest from handleInternalRequest ?
- External requests originate from a floor panel and route through the Dispatcher β the strategy can load-balance across cars.
// Who implements: NearestCarStrategy, ZoneStrategy, LookStrategy
// Who consumes: Dispatcher
interface DispatchStrategy {
ElevatorCar assign(Request request, List<ElevatorCar> available);
}
// Who implements: IdleState, MovingUpState, MovingDownState
// Who consumes: ElevatorCar.handleExternalRequest(), ElevatorCar.handleInternalRequest()
interface ElevatorState {
void handleExternalRequest(ElevatorCar car, Request request);
void handleInternalRequest(ElevatorCar car, int targetFloor);
boolean isIdle();
}
sequenceDiagram
participant U as User (Floor 3)
participant D as Dispatcher
participant S as DispatchStrategy
participant C as ElevatorCar
participant ST as ElevatorState
U->>D: handleExternalRequest(floor=3, UP)
D->>S: assign(request, availableCars)
S->>S: score each car by distance
S-->>D: bestCar (Car #2)
D->>C: handleExternalRequest(car, request)
C->>ST: delegate to current state
ST->>C: addStop(floor=3)
ST->>C: setState(MovingUpState)
C-->>D: stop added
Note over C: Car moves toward Floor 3 via SCAN
Observable outcome: External requests originate from a floor panel and route through the Dispatcher β the strategy can load-balance across cars.
Stage 6 β OOP Pillars in the Elevator Design
- Each of the four OOP pillars maps cleanly to a specific boundary in this design.
- The table below names the pillar, the class or interface that embodies it, and the concrete mechanism.
- Encapsulation deep-dive: The TreeSet<Integer inside ElevatorCar is the most critical encapsulation boundary in the design.
- If external code could add floors to the set arbitrarily, it could insert a below-current-floor stop while the car is sweeping up β silently breaking the SCAN invariant.
classDiagram
class Building {
+List~Floor~ floors
+List~ElevatorCar~ cars
+Dispatcher dispatcher
+handleExternalRequest(Request) void
}
class ElevatorCar {
+int id
+int currentFloor
-ElevatorState state
-TreeSet~Integer~ stopQueue
+addStop(int) void
+deferRequest(int) void
+move() void
+setState(ElevatorState) void
+getCurrentFloor() int
}
class Dispatcher {
-DispatchStrategy strategy
+assign(Request, List~ElevatorCar~) ElevatorCar
}
class ElevatorState {
<>
+handleExternalRequest(ElevatorCar, Request) void
+handleInternalRequest(ElevatorCar, int) void
+isIdle() boolean
}
class DispatchStrategy {
<>
+assign(Request, List~ElevatorCar~) ElevatorCar
}
class IdleState {
+handleExternalRequest(ElevatorCar, Request) void
+handleInternalRequest(ElevatorCar, int) void
+isIdle() boolean
}
class MovingUpState {
+handleExternalRequest(ElevatorCar, Request) void
+handleInternalRequest(ElevatorCar, int) void
+isIdle() boolean
}
class MovingDownState {
+handleExternalRequest(ElevatorCar, Request) void
+handleInternalRequest(ElevatorCar, int) void
+isIdle() boolean
}
class NearestCarStrategy {
+assign(Request, List~ElevatorCar~) ElevatorCar
}
class DestinationDispatchStrategy {
+assign(Request, List~ElevatorCar~) ElevatorCar
}
class Request {
+int sourceFloor
+Direction direction
}
class Direction {
<>
UP
DOWN
}
Building "1" *-- "many" ElevatorCar : contains
Building --> Dispatcher : uses
Dispatcher o-- DispatchStrategy : uses (injected)
ElevatorCar o-- ElevatorState : current state
ElevatorState <|.. IdleState : implements
ElevatorState <|.. MovingUpState : implements
ElevatorState <|.. MovingDownState : implements
DispatchStrategy <|.. NearestCarStrategy : implements
DispatchStrategy <|.. DestinationDispatchStrategy : implements
Dispatcher --> ElevatorCar : assigns
Request --> Direction : uses
Observable outcome: If external code could add floors to the set arbitrarily, it could insert a below-current-floor stop while the car is sweeping up β silently breaking the SCAN invariant.
End-to-end outcome
A hotel has 10 floors and 3 elevators.
Alternatives considered
Decision Guide: Choosing the Right Elevator Scheduling Strategy
Start with the SCAN algorithm and Nearest-Car dispatch.
Failure and degraded modes
Trade-offs & Failure Modes in Elevator Design
- Detection: Trade-offs & Failure Modes in Elevator Design
- User or operator impact: Trade-offs & Failure Modes in Elevator Design
