← Home
🏗Crack System Design with DSA AnimatorSystem DesignNEW
🏗 System Design›LLD Interview Prep›LLD
🛗

Design Elevator System

Multi-elevator dispatch, request queues, and direction-aware scheduling — a classic at Microsoft, Amazon, Goldman, and many OOP-focused LLD rounds.

📊 Medium → Hard⏱ 45–55 min🏢 Microsoft · Amazon · Goldman Sachs · Oracle · Samsung · most OOP-heavy LLD loops
🎯 Interview goal

Model Request (floor + direction), Elevator state (floor, direction, stops), ElevatorController facade, and DispatchStrategy to pick the best car. Interviewers want SCAN/LOOK-style scheduling explained, then clean Java.

📋

The interview question

“Design an elevator control system for a building.” Users press up/down at a floor or select a destination inside the car; the system assigns requests and moves elevators efficiently.

Focus on request model, elevator state, dispatch algorithm, and thread-safe step simulation — not building physics engines.

💡
Clarify number of elevators, floor range, and whether requests are hall (up/down) vs car (destination) — most interviews use both.
❓

Clarifying questions (ask before coding)

1How many elevators and floors?
Why askSets array sizes and whether brute-force dispatch is acceptable.
2Hall call vs car button — both?
Why askHall → Request with direction; car → destination-only stop.
3Optimization goal — minimize wait or minimize energy?
Why askPicks nearest idle vs SCAN/LOOK scheduling.
4Real-time simulation or event-driven steps?
Why askOften step() tick or processNextStop() — clarify interface.
✅

Requirements & assumptions (state aloud)

  • Functional: submit hall/car requests; controller assigns to an elevator; elevator moves floor-by-floor serving stops in current direction.
  • Elevator state: current floor, Direction (UP/DOWN/IDLE), pending stop set.
  • Dispatch: Strategy picks elevator for a new external request.
  • v1: single building, in-memory; synchronous step() loop for demo.
  • Out of scope: weight limits, maintenance mode, fire service — extensions.
🧩

High-level design & class relationships

ElevatorController is the facade — requestElevator(floor, direction) and selectFloor(elevatorId, floor).

Each Elevator maintains a TreeSet (or sorted set) of stops and reverses direction when no more stops ahead.

  1. Enums: Direction
  2. Domain: Request, Elevator
  3. Services: ElevatorController, DispatchStrategy
F3 ↑F2 —F1 ↓F0 G

↑ UP ↓ DOWN — IDLE G ground lobby

Elevator system overview
Requests → controller → elevators → floor stops
📐

UML class diagram (optional)

Sketch 5–6 classes if asked — controller, elevator, request, strategy.

Elevator system UML
Elevator, Request, ElevatorController, DispatchStrategy

Relationships to label aloud

  • Composition: ElevatorController → List<Elevator>.
  • Dependency: ElevatorController → DispatchStrategy.
  • Value object: Request (floor + direction + source).
🎨

Design patterns used (and why)

PatternWhereWhy (interview soundbite)
🪞FacadeElevatorControllerSingle entry for hall and car requests.
🎯StrategyDispatchStrategySwap nearest-car vs load-balanced dispatch.
🎬StateElevator direction + stopsMovement rules depend on UP/DOWN/IDLE.
👁️Observer (ext)floor display panelsNotify UI when elevator arrives at floor.
💻

Full solution — Request & Elevator

1. Direction & Requestjava
public enum Direction { UP, DOWN, IDLE }

public class Request {
    public enum Source { HALL, CAR }

    private final int floor;
    private final Direction direction;
    private final Source source;
    private final Instant createdAt = Instant.now();

    // Hall call: user pressed UP or DOWN at a floor
    public static Request hallCall(int floor, Direction direction) {
        return new Request(floor, direction, Source.HALL);
    }

    // Car call: passenger selected destination inside elevator
    public static Request carCall(int destinationFloor) {
        return new Request(destinationFloor, Direction.IDLE, Source.CAR);
    }

    // Private ctor — use factory methods for hall vs car semantics
    private Request(int floor, Direction direction, Source source) {
        this.floor = floor;
        this.direction = direction;
        this.source = source;
    }

    // Returns requested floor (hall) or destination (car)
    public int getFloor() { return floor; }

    // Returns UP/DOWN for hall calls; IDLE for car destination-only
    public Direction getDirection() { return direction; }

    // Distinguishes external hall button from in-car panel
    public Source getSource() { return source; }

    // Timestamp for FIFO tie-breaking or metrics
    public Instant getCreatedAt() { return createdAt; }
}
2. Elevatorjava
public class Elevator {
    private final int id;
    private final int minFloor;
    private final int maxFloor;
    private int currentFloor;
    private Direction direction = Direction.IDLE;
    private final TreeSet<Integer> upStops = new TreeSet<>();
    private final TreeSet<Integer> downStops = new TreeSet<>(Comparator.reverseOrder());

    // Creates elevator with id and servicable floor range
    public Elevator(int id, int minFloor, int maxFloor, int startFloor) {
        this.id = id;
        this.minFloor = minFloor;
        this.maxFloor = maxFloor;
        this.currentFloor = startFloor;
    }

    // Adds a stop from hall or car request; sets direction if idle
    public synchronized void addRequest(Request request) {
        int floor = request.getFloor();
        validateFloor(floor);
        if (request.getSource() == Request.Source.CAR) {
            addStop(floor);
            return;
        }
        if (request.getDirection() == Direction.UP) upStops.add(floor);
        else if (request.getDirection() == Direction.DOWN) downStops.add(floor);
        if (direction == Direction.IDLE) {
            direction = floor >= currentFloor ? Direction.UP : Direction.DOWN;
        }
    }

    // Routes stop to up or down queue based on current direction context
    private void addStop(int floor) {
        if (floor > currentFloor || (floor == currentFloor && direction == Direction.UP))
            upStops.add(floor);
        else
            downStops.add(floor);
        if (direction == Direction.IDLE)
            direction = floor >= currentFloor ? Direction.UP : Direction.DOWN;
    }

    // Moves one floor toward next stop; opens door if at stop; returns true if moved or stopped
    public synchronized boolean step() {
        if (shouldStopHere()) {
            openDoors();
            return true;
        }
        if (direction == Direction.UP && !upStops.isEmpty()) {
            currentFloor++;
            return true;
        }
        if (direction == Direction.DOWN && !downStops.isEmpty()) {
            currentFloor--;
            return true;
        }
        switchDirectionIfNeeded();
        return direction != Direction.IDLE;
    }

    // True when current floor is in the active direction's stop set
    private boolean shouldStopHere() {
        return (direction == Direction.UP && upStops.contains(currentFloor))
            || (direction == Direction.DOWN && downStops.contains(currentFloor));
    }

    // Removes current floor from queues and goes IDLE if no more stops
    private void openDoors() {
        upStops.remove(currentFloor);
        downStops.remove(currentFloor);
        switchDirectionIfNeeded();
    }

    // Flips UP↔DOWN or IDLE when current direction queue is empty
    private void switchDirectionIfNeeded() {
        if (direction == Direction.UP && upStops.isEmpty()) {
            direction = downStops.isEmpty() ? Direction.IDLE : Direction.DOWN;
        } else if (direction == Direction.DOWN && downStops.isEmpty()) {
            direction = upStops.isEmpty() ? Direction.IDLE : Direction.UP;
        } else if (direction == Direction.IDLE) {
            if (!upStops.isEmpty()) direction = Direction.UP;
            else if (!downStops.isEmpty()) direction = Direction.DOWN;
        }
    }

    // Throws if floor outside building range
    private void validateFloor(int floor) {
        if (floor < minFloor || floor > maxFloor)
            throw new IllegalArgumentException("Invalid floor: " + floor);
    }

    // Estimated cost to serve a hall request — used by dispatch strategies
    public synchronized int estimatedCost(Request request) {
        int target = request.getFloor();
        int distance = Math.abs(currentFloor - target);
        if (direction == Direction.IDLE) return distance;
        if (direction == Direction.UP && target >= currentFloor) return distance;
        if (direction == Direction.DOWN && target <= currentFloor) return distance;
        return distance + 10; // penalty if elevator must reverse first
    }

    // True when no pending stops and direction is IDLE
    public synchronized boolean isIdle() {
        return direction == Direction.IDLE && upStops.isEmpty() && downStops.isEmpty();
    }

    // Returns elevator id for controller routing
    public int getId() { return id; }

    // Returns current floor for display and dispatch
    public int getCurrentFloor() { return currentFloor; }

    // Returns movement direction for scheduling logic
    public Direction getDirection() { return direction; }
}
⚙️

Full solution — Controller & dispatch

3. DispatchStrategyjava
public interface DispatchStrategy {
    // Returns best elevator to handle this hall request
    Elevator selectElevator(Request request, List<Elevator> elevators);
}

public class NearestElevatorStrategy implements DispatchStrategy {
    // Picks elevator with lowest estimatedCost; prefers idle cars on ties
    @Override
    public Elevator selectElevator(Request request, List<Elevator> elevators) {
        return elevators.stream()
            .min(Comparator
                .comparingInt((Elevator e) -> e.estimatedCost(request))
                .thenComparing(e -> e.isIdle() ? 0 : 1))
            .orElseThrow();
    }
}

public class LookScanStrategy implements DispatchStrategy {
    // Same cost function but strongly prefers elevators already moving toward request
    @Override
    public Elevator selectElevator(Request request, List<Elevator> elevators) {
        int floor = request.getFloor();
        Direction dir = request.getDirection();
        Optional<Elevator> movingMatch = elevators.stream()
            .filter(e -> e.getDirection() == dir)
            .filter(e -> dir == Direction.UP ? e.getCurrentFloor() <= floor : e.getCurrentFloor() >= floor)
            .min(Comparator.comparingInt(e -> Math.abs(e.getCurrentFloor() - floor)));
        return movingMatch.orElseGet(() -> new NearestElevatorStrategy().selectElevator(request, elevators));
    }
}
4. ElevatorController + demojava
public class ElevatorController {
    private final List<Elevator> elevators;
    private final DispatchStrategy dispatchStrategy;
    private final int minFloor;
    private final int maxFloor;

    // Builds controller with floor bounds, elevator fleet, and dispatch policy
    public ElevatorController(int minFloor, int maxFloor, List<Elevator> elevators, DispatchStrategy dispatchStrategy) {
        this.minFloor = minFloor;
        this.maxFloor = maxFloor;
        this.elevators = List.copyOf(elevators);
        this.dispatchStrategy = dispatchStrategy;
    }

    // Hall button pressed — validates floor/direction and routes to best elevator
    public void requestElevator(int floor, Direction direction) {
        validateFloor(floor);
        if (direction == Direction.IDLE)
            throw new IllegalArgumentException("Hall call needs UP or DOWN");
        Request req = Request.hallCall(floor, direction);
        Elevator chosen = dispatchStrategy.selectElevator(req, elevators);
        chosen.addRequest(req);
    }

    // In-car destination button — adds stop to specific elevator
    public void selectFloor(int elevatorId, int destinationFloor) {
        validateFloor(destinationFloor);
        Elevator e = findElevator(elevatorId);
        e.addRequest(Request.carCall(destinationFloor));
    }

    // Advances all elevators one simulation tick; returns true if any car moved
    public boolean stepAll() {
        boolean any = false;
        for (Elevator e : elevators) {
            if (e.step()) any = true;
        }
        return any;
    }

    // Returns read-only fleet for monitoring dashboards
    public List<Elevator> getElevators() {
        return Collections.unmodifiableList(elevators);
    }

    // Finds elevator by id or throws if unknown
    private Elevator findElevator(int id) {
        return elevators.stream()
            .filter(e -> e.getId() == id)
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("Unknown elevator: " + id));
    }

    // Ensures floor is within building bounds
    private void validateFloor(int floor) {
        if (floor < minFloor || floor > maxFloor)
            throw new IllegalArgumentException("Invalid floor: " + floor);
    }
}

public class ElevatorDemo {
    // Simulates hall call at floor 3 UP and runs until all cars idle
    public static void main(String[] args) {
        List<Elevator> cars = List.of(
            new Elevator(1, 0, 10, 0),
            new Elevator(2, 0, 10, 5)
        );
        ElevatorController controller = new ElevatorController(
            0, 10, cars, new NearestElevatorStrategy());

        controller.requestElevator(3, Direction.UP);
        controller.selectFloor(1, 7);

        for (int i = 0; i < 20 && controller.stepAll(); i++) {
            // simulation tick
        }
        controller.getElevators().forEach(e ->
            System.out.println("Elevator " + e.getId() + " at F" + e.getCurrentFloor()));
    }
}
🎤

While you code — questions interviewers ask

🚀

Extensions & follow-ups

  • Priority: fire service / VIP override queue.
  • Weight sensor: skip floor when car full.
  • Multiple banks: separate controllers per shaft group.
  • Predictive dispatch: ML on traffic patterns — mention only if senior loop.
⚠️

Common mistakes (fail signals)

  • No direction reversal — elevator stuck after one trip.
  • Dispatch always to elevator 0 — ignores cost/wait time.
  • Mixing hall and car requests without Request.Source.
  • Using unsorted list for stops — O(n) scan every floor.
  • Forgetting bounds check on floor numbers.
🧾

60-second cheat sheet

Ask firstFloors, # elevators, hall vs car, goal (wait vs energy)
Core classesRequest, Elevator, ElevatorController, DispatchStrategy
SchedulingStops in TreeSet; SCAN/LOOK reversal
PatternsFacade, Strategy, State on direction
APIrequestElevator(floor, dir); selectFloor(id, dest); stepAll()
🔗

More LLD problems