SDE Path

Walkthrough: Design an Elevator System

10 min read

Entities before behaviour

Model the pieces: an Elevator at a floor, a Request (target floor + direction), and a controller that assigns requests. Keep direction and motion explicit.

enum Direction { UP, DOWN, IDLE }

class Request {
  final int floor;
  final Direction dir;      // direction the rider wants to go (hall call)
  Request(int floor, Direction dir) { this.floor = floor; this.dir = dir; }
}

class Elevator {
  final int id;
  int currentFloor = 0;
  Direction dir = Direction.IDLE;
  ElevatorState state;
  final TreeSet<Integer> up = new TreeSet<>();                        // stops above, ascending
  final TreeSet<Integer> down = new TreeSet<>(Comparator.reverseOrder());  // stops below, descending
  Elevator(int id) { this.id = id; }
}

The two sorted sets — up and down — are the core insight: they let the car serve stops in travel order instead of arrival order, which would make it yo-yo across the building.

<!--STEP-->

State, not a tangle of flags

An elevator is idle, moving, or doors-open, and the legal actions differ per state (you can't open doors mid-shaft). Encoding that with booleans (isMoving, doorsOpen) breeds impossible combinations. Model states explicitly.

interface ElevatorState {
  ElevatorState step(Elevator e);     // advance one tick, return the next state
}

class MovingState implements ElevatorState {
  public ElevatorState step(Elevator e) {
    TreeSet<Integer> queue = e.dir == Direction.UP ? e.up : e.down;
    if (queue.isEmpty()) return new IdleState();
    int target = queue.first();
    e.currentFloor += (e.dir == Direction.UP ? 1 : -1);
    if (e.currentFloor == target) { queue.pollFirst(); return new DoorOpenState(); }
    return this;
  }
}

Each state owns its own transitions, so "what can happen next" is local and total — no if (isMoving && !doorsOpen && ...) ladders that grow with every feature.

<!--STEP-->

Serve the current direction first (LOOK)

A good scheduler doesn't reverse the moment a lower floor calls — it keeps going up, serving every up-stop on the way, then sweeps down. That's the LOOK algorithm.

void addStop(Elevator e, int floor) {
  if (floor > e.currentFloor) e.up.add(floor);
  else if (floor < e.currentFloor) e.down.add(floor);
  if (e.dir == Direction.IDLE)
    e.dir = floor > e.currentFloor ? Direction.UP : Direction.DOWN;
}

Because up is ascending and down is descending, queue.first() in the moving state always returns the next stop in the direction of travel. Requests behind the car simply wait for the return sweep — minimal total travel, no thrashing.

<!--STEP-->

Dispatching to the best car

With multiple elevators, a controller picks one per hall call. A workable heuristic: prefer a car already moving toward the floor in the same direction; otherwise the nearest idle car.

Elevator pick(List<Elevator> cars, Request r) {
  return cars.stream().min(Comparator.comparingInt(c -> cost(c, r))).orElse(null);
}
int cost(Elevator c, Request r) {
  int dist = Math.abs(c.currentFloor - r.floor);
  boolean sameWay = c.dir == r.dir || c.dir == Direction.IDLE;
  return sameWay ? dist : dist + 1000;      // penalise cars heading the wrong way
}

The controller only scores and selects; each elevator still owns its own movement. That separation — dispatch vs motion — is what keeps the design extensible: a smarter cost function is a one-method change.

<!--STEP-->

Tie it together

class ElevatorController {
  private final List<Elevator> cars;
  ElevatorController(List<Elevator> cars) { this.cars = cars; }

  void requestPickup(Request r) { addStop(pick(cars, r), r.floor); }

  void tick() {                              // called by a clock
    for (Elevator e : cars) e.state = e.state.step(e);
  }
}

Each concern is isolated: Request/Direction model intent, ElevatorState owns transitions, addStop/LOOK own ordering, and the controller owns dispatch. To add express elevators or VIP priority you touch exactly one of those — the hallmark of a design that survives follow-up questions.

Practice it for real: the Elevator problem in the ladder drives these dispatch and movement rules through hidden scenarios.

Check yourself

5 questions — graded when you submit.

Question 1

Why split an elevator's pending stops into two direction-sorted TreeSets (up ascending, down descending)?

Question 2

Why model the elevator's mode with explicit state objects instead of boolean flags like isMoving/doorsOpen?

Question 3

Under the LOOK approach, how does the car handle a call for a floor behind its current direction?

Question 4

What makes the dispatcher's cost heuristic reasonable?

Question 5

Why is this elevator design considered extensible?

Finished reading?

Sign in to save your progress across lessons.