ayushsalampuriya.xyz Revise

LLD · Simulation

Elevator Control System

A small interview design for elevator classes, dispatch, and SCAN movement. Further reading: Hello Interview — Elevator.

Simple summary: The controller chooses an elevator for each hall call. Every elevator owns its stops and keeps moving in one direction until no useful stop remains ahead, then reverses (SCAN). Example: You press UP on floor 3 — pick an elevator going UP below floor 3; it stops at 3 on the way up before serving higher destination floors.

The system manages several elevators. A controller assigns hall calls, while each elevator owns its requests and moves one simulated tick at a time through step().

1. Problem understanding and requirements

Prompt: “Design an elevator control system for a building.” Before coding, agree on the scale, request types, simulation model, and exclusions.

Clarifying questions (interview script)

FeatureRequirement
Capacity3 elevators, floors 0–9
Hall callFloor + direction (UP / DOWN)
DestinationFloor only (selected inside the elevator)
Simulationcontroller.step() advances all elevators one tick
ValidationReturn false for invalid floor; ignore same-floor requests

2. Core entities

Use three main classes. Each class owns the state and behavior it understands best.

// Who talks to whom Client / Simulation → ElevatorController.requestElevator(floor, type) → ElevatorController.requestDestination(elevatorId, floor) → ElevatorController.step() → for each Elevator: elevator.step()

3. Enums and value objects

Enums limit direction and request type to known values.

enum Direction { UP, DOWN, IDLE } enum RequestType { PICKUP_UP, PICKUP_DOWN, DESTINATION } class Request { int floor RequestType type Request(int floor, RequestType type) { this.floor = floor this.type = type } // Value object: equals/hashCode on (floor, type) }

4. Class design

Elevator

Use a Set<Request> so repeated presses for the same floor and request type create only one pending request.

class Elevator { int id int currentFloor // 0..9 Direction direction // UP | DOWN | IDLE Set<Request> requests void addRequest(Request req) { if (req.floor == currentFloor) return // no-op requests.add(req) } void removeRequest(int floor, RequestType type) { ... } void step() { // movement + stop logic (see section 6) } }

ElevatorController

class ElevatorController { List<Elevator> elevators // size 3 boolean requestElevator(int floor, RequestType hallType) { if (!isValidFloor(floor)) return false if (hallType == DESTINATION) return false // hall only Request req = new Request(floor, hallType) Elevator best = selectBestElevator(req) best.addRequest(req) return true } boolean requestDestination(int elevatorId, int floor) { if (!isValidFloor(floor)) return false Elevator e = elevators.get(elevatorId) e.addRequest(new Request(floor, DESTINATION)) return true } void step() { for (Elevator e : elevators) e.step() } }

5. Dispatch: picking the best elevator

When someone presses UP on floor 5, the controller must choose one elevator.

StrategyIdeaProblem
Nearest Min distance to floor May pick an elevator moving away from the call
Direction-aware Prefer elevator moving toward floor in same direction Ignores whether the lift has already passed the floor
Request-aware (best) Direction + position: elevator will pass the floor before reversing Slightly more code — preferred in interviews

Trap: same direction and nearest is not enough

You are on floor 4 and press DOWN (want ground). Elevator A is on floor 3 going DOWN.

A simple rule might pick A because it has the same direction and is only one floor away. That choice is wrong: A is below floor 4 and moving farther away.

Correct rule: For a DOWN call at floor F, prefer an elevator above F that is already moving down. For an UP call, prefer one below F that is already moving up. An idle elevator is the next choice.

Elevator selectBestElevator(Request req) { // Priority 1: elevator that will naturally pass req.floor // UP call at F → elevator going UP AND currentFloor < F // DOWN call at F → elevator going DOWN AND currentFloor > F Elevator candidate = findServingElevator(req) if (candidate != null) return candidate // Priority 2: nearest IDLE elevator candidate = findNearestIdle(req.floor) if (candidate != null) return candidate // Priority 3: nearest elevator (will reverse via SCAN later) return findNearest(req.floor) }

Example: Hall call at floor 3 UP. Elevator A at floor 1 going UP → good. Elevator B at floor 6 going DOWN → worse; it must reverse before serving 3 UP.

6. Movement: SCAN (scanning) algorithm

With SCAN, an elevator keeps moving in one direction and serves matching stops along the way. When no request remains ahead, it reverses if requests exist behind it; otherwise, it becomes IDLE.

This avoids the repeated back-and-forth movement that a simple FIFO queue can cause.

void Elevator.step() { if (requests.isEmpty()) { direction = IDLE return } if (direction == IDLE) { Request nearest = findNearestRequest() direction = (nearest.floor > currentFloor) ? UP : DOWN } if (shouldStop()) { removeMatchingRequestsAtCurrentFloor() return // stopping costs one full tick } if (!hasRequestsAhead(direction)) { if (hasRequestsInOppositeDirection(direction)) { direction = flip(direction) // reverse — one full tick } else { direction = IDLE } return } // scan: move one floor in current direction currentFloor += (direction == UP) ? 1 : -1 }

Direction-aware stopping

Only stop for hall calls that match travel direction.

boolean shouldStop() { for (Request req : requests) { if (req.floor != currentFloor) continue if (req.type == DESTINATION) return true if (req.type == PICKUP_UP && direction == UP) return true if (req.type == PICKUP_DOWN && direction == DOWN) return true } return false } boolean hasRequestsAhead(Direction dir) { for (Request req : requests) { if (dir == UP && req.floor > currentFloor) return true if (dir == DOWN && req.floor < currentFloor) return true } return false }

7. Simulation loop

// Driver (tests or demo) controller.requestElevator(0, PICKUP_UP) // lobby wants up controller.requestDestination(0, 7) // passenger in elevator 0 for (int t = 0; t < 100; t++) { controller.step() printState() } Walkthrough (elevator 0 starts at floor 0, going UP): tick 1–7: move UP, stop at floor 7 (destination) if floor 3 had a PICKUP_UP request and elevator passed it while going UP → stop at 3 first, then continue to 7

8. Concurrency

A hall call may arrive while step() is running. One thread could modify requests during iteration, or two threads could assign the same idle elevator.

// Option B sketch ConcurrentLinkedQueue<Request> inbound void addRequest(Request req) { inbound.add(req) } void step() { drainInboundIntoSet() // ... SCAN logic }

9. Extensibility and interview depth

Quick revision

  • Scope: 3 elevators, floors 0–9, and a step() simulation; doors and capacity are excluded.
  • Entities: Controller (dispatch), Elevator (movement), Request (floor + type).
  • Request types: PICKUP_UP, PICKUP_DOWN, DESTINATION.
  • Dispatch: Serving elevator → idle nearest → any nearest.
  • Dispatch trap: Direction alone is not enough; the elevator must also be approaching the caller.
  • SCAN / scanning: Lift moves in one direction, checks each floor; serves all stops that way; when none ahead, reverse if opposite requests exist, else IDLE.
  • Movement: Stop only if hall-call direction matches travel direction.
  • State: Use a Set<Request> for deduplication and IDLE when no request remains.
  • Concurrency: Protect shared state with a mutex or drain a thread-safe input queue each tick.