Skip to main content

Elevator System

Difficulty: Hard | Frequency: High | Patterns: State, Observer, Strategy


Interview Expectationโ€‹

The Elevator problem is famous for the scheduling algorithm discussion. Interviewers want to see:

ExpectationDetails
Elevator state machineIDLE, MOVING_UP, MOVING_DOWN, DOOR_OPEN
Request handlingInternal (button inside car) + External (hall call)
Scheduling strategyFCFS, SCAN (elevator algorithm), LOOK
Multi-elevatorDispatch logic โ€” which elevator handles which request
Thread safetyElevator state modified from request threads

Step 1: Clarify Requirementsโ€‹

  • How many elevators? โ†’ configurable (start with 1, extend to N)
  • How many floors? โ†’ configurable
  • Request types? โ†’ External: floor + direction. Internal: destination floor
  • Scheduling algorithm? โ†’ SCAN (elevator algorithm) โ€” mention options
  • Emergency/priority? โ†’ out of scope for interview

Step 2: State Machineโ€‹

IDLE โ†’ request arrives โ†’ MOVING_UP or MOVING_DOWN
MOVING_UP โ†’ reaches destination โ†’ DOOR_OPEN
DOOR_OPEN โ†’ door timer expires โ†’ IDLE or MOVING (if more requests)
MOVING_UP โ†’ emergency stop โ†’ IDLE

Step 3: Core Classesโ€‹

public enum Direction { UP, DOWN }
public enum ElevatorState { IDLE, MOVING_UP, MOVING_DOWN, DOOR_OPEN }

// Request types
public record ExternalRequest(int floor, Direction direction) {}
public record InternalRequest(int destinationFloor) {}

// โ”€โ”€ Elevator โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
public class Elevator {
private final int id;
private int currentFloor;
private ElevatorState state;
private final TreeSet<Integer> upRequests = new TreeSet<>(); // destinations going up
private final TreeSet<Integer> downRequests = new TreeSet<>(Collections.reverseOrder()); // going down

private final Object lock = new Object();

public Elevator(int id, int initialFloor) {
this.id = id;
this.currentFloor = initialFloor;
this.state = ElevatorState.IDLE;
}

public void addRequest(int floor) {
synchronized (lock) {
if (floor > currentFloor || state == ElevatorState.MOVING_UP) {
upRequests.add(floor);
} else {
downRequests.add(floor);
}
if (state == ElevatorState.IDLE) {
processNext();
}
}
}

// SCAN algorithm: serve all up requests, then all down requests
private void processNext() {
if (!upRequests.isEmpty()) {
int destination = upRequests.first();
moveToFloor(destination, Direction.UP);
} else if (!downRequests.isEmpty()) {
int destination = downRequests.first();
moveToFloor(destination, Direction.DOWN);
} else {
state = ElevatorState.IDLE;
}
}

private void moveToFloor(int destination, Direction direction) {
state = direction == Direction.UP ? ElevatorState.MOVING_UP : ElevatorState.MOVING_DOWN;
System.out.printf("Elevator %d moving %s from floor %d to floor %d%n",
id, direction, currentFloor, destination);

// Simulate movement (in real system, this would be event-driven)
currentFloor = destination;

if (direction == Direction.UP) upRequests.remove(destination);
else downRequests.remove(destination);

state = ElevatorState.DOOR_OPEN;
System.out.printf("Elevator %d: doors open at floor %d%n", id, currentFloor);

// Simulate door close
state = ElevatorState.IDLE;
processNext(); // continue to next request
}

public int getCurrentFloor() { synchronized (lock) { return currentFloor; } }
public ElevatorState getState() { synchronized (lock) { return state; } }
public int getId() { return id; }
}

// โ”€โ”€ Dispatcher โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
public class ElevatorDispatcher {
private final List<Elevator> elevators;

public ElevatorDispatcher(int numElevators, int numFloors) {
this.elevators = new ArrayList<>();
for (int i = 1; i <= numElevators; i++) {
elevators.add(new Elevator(i, 1)); // all start at floor 1
}
}

// Assign the request to the nearest idle or same-direction elevator
public void dispatch(ExternalRequest request) {
Elevator best = elevators.stream()
.min(Comparator.comparingInt(e -> cost(e, request)))
.orElseThrow();
best.addRequest(request.floor());
System.out.printf("Dispatching floor %d request to Elevator %d%n",
request.floor(), best.getId());
}

// Cost function: distance + penalty for wrong direction
private int cost(Elevator elevator, ExternalRequest request) {
int distance = Math.abs(elevator.getCurrentFloor() - request.floor());
boolean sameDirection = (elevator.getState() == ElevatorState.MOVING_UP
&& request.direction() == Direction.UP)
|| (elevator.getState() == ElevatorState.MOVING_DOWN
&& request.direction() == Direction.DOWN);
return distance + (elevator.getState() == ElevatorState.IDLE || sameDirection ? 0 : 10);
}
}

Scheduling Algorithm Comparisonโ€‹

AlgorithmDescriptionProsCons
FCFSFirst-come, first-servedSimpleHigh travel distance
SCANGo up serving all, then downLow avg waitLast floor in direction waits
LOOKLike SCAN but reverses at last request, not top/bottomMore efficient than SCANSlightly complex
C-SCANCircular: always goes up, jumps to bottomUniform wait timesLonger travel
Senior Deep Dive ๐Ÿ”ด

Real elevators use LOOK (a variant of SCAN) because it avoids traveling to the top/bottom floor when there are no requests there. The key insight: reverse direction at the last pending request, not at the physical boundary.


Interview Checklistโ€‹

  • ElevatorState enum covers all states (IDLE, MOVING_UP, MOVING_DOWN, DOOR_OPEN)
  • Separate up/down request queues (TreeSet for sorted order)
  • SCAN algorithm implemented correctly
  • Dispatcher assigns based on cost function (distance + direction match)
  • Synchronized access to elevator state
  • Discussed multiple scheduling algorithms and trade-offs