LLD: Design a Simple Elevator System - Multiple Lifts
Simple Elevator System with Multiple Lifts, focusing on object modeling, interactions, thread safety, and extensibility
Let’s look into FR
Scope & Assumptions
In scope
Multiple elevators in a building
Floors:
0 … NRequests:
External:
UP / DOWNfrom a floorInternal: destination floor from inside elevator
Simple scheduling (not ML / not destination dispatch)
Concurrent requests
Out of scope (explicitly)
Fire mode, maintenance mode
Load/weight sensors
Advanced traffic optimization
Core Entities (High-Level)
Building
├── ElevatorController
│ ├── List<Elevator>
│ └── Scheduler
├── Elevator (multiple)
│ ├── Door
│ ├── Motor
│ └── RequestQueue
└── Request
Enums
enum Direction {
UP, DOWN, IDLE
}
enum ElevatorState {
MOVING, IDLE, DOOR_OPEN
}
enum RequestType {
INTERNAL, EXTERNAL
}
4. Request Model
class Request {
int sourceFloor;
int destinationFloor;
Direction direction;
RequestType type;
public Request(int source, int dest, RequestType type) {
this.sourceFloor = source;
this.destinationFloor = dest;
this.type = type;
this.direction = dest > source ? Direction.UP : Direction.DOWN;
}
}
5. Elevator Class (Core Logic)
Each elevator runs independently (usually one thread per elevator).
class Elevator implements Runnable {
private final int id;
private int currentFloor;
private Direction direction;
private ElevatorState state;
// Min heap for UP, Max heap for DOWN
private PriorityQueue<Integer> upQueue;
private PriorityQueue<Integer> downQueue;
public Elevator(int id) {
this.id = id;
this.currentFloor = 0;
this.direction = Direction.IDLE;
this.state = ElevatorState.IDLE;
upQueue = new PriorityQueue<>();
downQueue = new PriorityQueue<>(Collections.reverseOrder());
}
public synchronized void addRequest(Request request) {
if (request.direction == Direction.UP) {
upQueue.offer(request.destinationFloor);
} else {
downQueue.offer(request.destinationFloor);
}
notify(); // wake up elevator thread
}
@Override
public void run() {
while (true) {
processRequests();
}
}
private synchronized void processRequests() {
try {
while (upQueue.isEmpty() && downQueue.isEmpty()) {
direction = Direction.IDLE;
state = ElevatorState.IDLE;
wait();
}
if (direction == Direction.IDLE) {
direction = !upQueue.isEmpty() ? Direction.UP : Direction.DOWN;
}
if (direction == Direction.UP) {
moveUp();
} else {
moveDown();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void moveUp() {
while (!upQueue.isEmpty()) {
int nextFloor = upQueue.poll();
moveToFloor(nextFloor);
}
direction = Direction.DOWN;
}
private void moveDown() {
while (!downQueue.isEmpty()) {
int nextFloor = downQueue.poll();
moveToFloor(nextFloor);
}
direction = Direction.UP;
}
private void moveToFloor(int floor) {
state = ElevatorState.MOVING;
System.out.println("Elevator " + id + " moving from " +
currentFloor + " to " + floor);
currentFloor = floor;
openDoor();
}
private void openDoor() {
state = ElevatorState.DOOR_OPEN;
System.out.println("Elevator " + id + " opening door at floor " + currentFloor);
closeDoor();
}
private void closeDoor() {
System.out.println("Elevator " + id + " closing door");
state = ElevatorState.IDLE;
}
public int getCurrentFloor() {
return currentFloor;
}
public Direction getDirection() {
return direction;
}
public boolean isIdle() {
return state == ElevatorState.IDLE;
}
}
6. Scheduler (Brain of the System)
Responsible for assigning requests to elevators.
Simple Strategy (Interview-Friendly)
Prefer idle elevator
Else choose elevator moving in same direction & closest
class Scheduler {
public Elevator selectElevator(List<Elevator> elevators, Request request) {
Elevator best = null;
int minDistance = Integer.MAX_VALUE;
for (Elevator elevator : elevators) {
if (elevator.isIdle()) {
return elevator;
}
if (elevator.getDirection() == request.direction) {
int distance = Math.abs(elevator.getCurrentFloor() - request.sourceFloor);
if (distance < minDistance) {
minDistance = distance;
best = elevator;
}
}
}
return best != null ? best : elevators.get(0); // fallback
}
}
7. ElevatorController
Single entry point for external + internal requests.
class ElevatorController {
private List<Elevator> elevators;
private Scheduler scheduler;
public ElevatorController(int numElevators) {
elevators = new ArrayList<>();
scheduler = new Scheduler();
for (int i = 0; i < numElevators; i++) {
Elevator elevator = new Elevator(i);
elevators.add(elevator);
new Thread(elevator).start();
}
}
public void handleExternalRequest(int floor, Direction direction) {
Request request = new Request(
floor,
direction == Direction.UP ? floor + 1 : floor - 1,
RequestType.EXTERNAL
);
Elevator elevator = scheduler.selectElevator(elevators, request);
elevator.addRequest(request);
}
public void handleInternalRequest(int elevatorId, int destinationFloor) {
Elevator elevator = elevators.get(elevatorId);
Request request = new Request(
elevator.getCurrentFloor(),
destinationFloor,
RequestType.INTERNAL
);
elevator.addRequest(request);
}
}
8. Thread Safety
| Component | Strategy |
| ----------------- | ----------------------- |
| Elevator queues | `synchronized` |
| Scheduler | Read-only |
| Controller | Single-threaded entry |
| Elevator movement | One thread per elevator |
9. How Requests Flow (Explain in Interview)
User presses UP/DOWN →
ElevatorControllerController creates
RequestSchedulerpicks best elevatorRequest added to elevator queue
Elevator thread wakes up and processes floors sequentially
10. Time & Space Complexity
| Operation | Complexity |
| ------------------ | --------------------- |
| Elevator selection | `O(N)` |
| Insert request | `O(log F)` |
| Memory | `O(N * F)` worst case |

