Skip to content
1 min read

Design an Elevator System

Interview Context

Asked at: Google, Amazon, Microsoft, Apple | Level: L4-L6 | Time: 45 minutes | Type: LLD/OOP Design


Requirements

Functional

  • Building with N floors and M elevators
  • Passengers request elevators from any floor (up/down button)
  • Passengers select destination floor inside the elevator
  • Elevator moves efficiently using scheduling algorithm (SCAN/LOOK)
  • Display current floor and direction on each floor
  • Handle door open/close with timeout

Non-Functional

  • Minimize average passenger wait time
  • No starvation — every request eventually served
  • Handle concurrent requests from multiple floors
  • Graceful degradation if one elevator goes out of service

Class Diagram

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
classDiagram
    class ElevatorSystem {
        -List~Elevator~ elevators
        -SchedulingStrategy strategy
        +requestElevator(int floor, Direction dir) void
        +status() List~ElevatorStatus~
    }

    class Elevator {
        -int id
        -int currentFloor
        -Direction direction
        -ElevatorState state
        -TreeSet~Integer~ upStops
        -TreeSet~Integer~ downStops
        +addStop(int floor) void
        +move() void
        +openDoor() void
        +closeDoor() void
    }

    class Request {
        -int sourceFloor
        -Direction direction
        -long timestamp
    }

    class SchedulingStrategy {
        <<interface>>
        +selectElevator(List~Elevator~, Request) Elevator
    }

    class ScanStrategy {
        +selectElevator(List~Elevator~, Request) Elevator
    }

    class ShortestSeekStrategy {
        +selectElevator(List~Elevator~, Request) Elevator
    }

    class ElevatorState {
        <<enumeration>>
        IDLE
        MOVING_UP
        MOVING_DOWN
        DOOR_OPEN
        MAINTENANCE
    }

    class Direction {
        <<enumeration>>
        UP
        DOWN
    }

    ElevatorSystem "1" --> "*" Elevator
    ElevatorSystem --> SchedulingStrategy
    ScanStrategy ..|> SchedulingStrategy
    ShortestSeekStrategy ..|> SchedulingStrategy
    Elevator --> ElevatorState
    Elevator --> Direction
    Request --> Direction

Key Design Decisions

Decision Choice Why
Scheduling Strategy Pattern SCAN, LOOK, Shortest Seek are interchangeable
Elevator movement State Machine Clear transitions: IDLE → MOVING → DOOR_OPEN → MOVING
Stop management Two TreeSets (up/down) O(log n) insert, natural ordering for SCAN
Floor displays Observer Pattern Elevators notify displays on state change
Concurrency Synchronized stop sets Multiple floor requests arrive simultaneously

Java Implementation

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

public class Request {
    private final int sourceFloor;
    private final Direction direction;
    private final long timestamp;

    public Request(int sourceFloor, Direction direction) {
        this.sourceFloor = sourceFloor;
        this.direction = direction;
        this.timestamp = System.currentTimeMillis();
    }
    // getters
}

public class Elevator {
    private final int id;
    private int currentFloor;
    private Direction direction;
    private ElevatorState state;
    private final TreeSet<Integer> upStops = new TreeSet<>();
    private final TreeSet<Integer> downStops = new TreeSet<>();

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

    public synchronized void addStop(int floor) {
        if (floor > currentFloor) upStops.add(floor);
        else if (floor < currentFloor) downStops.add(floor);
        else openDoor(); // already at requested floor
    }

    public void move() {
        if (state == ElevatorState.MOVING_UP) {
            Integer next = upStops.ceiling(currentFloor);
            if (next != null) {
                currentFloor = next;
                upStops.remove(next);
                openDoor();
            } else {
                // Reverse direction (SCAN behavior)
                direction = Direction.DOWN;
                state = downStops.isEmpty() ? ElevatorState.IDLE : ElevatorState.MOVING_DOWN;
            }
        } else if (state == ElevatorState.MOVING_DOWN) {
            Integer next = downStops.floor(currentFloor);
            if (next != null) {
                currentFloor = next;
                downStops.remove(next);
                openDoor();
            } else {
                direction = Direction.UP;
                state = upStops.isEmpty() ? ElevatorState.IDLE : ElevatorState.MOVING_UP;
            }
        }
    }

    public void openDoor() { state = ElevatorState.DOOR_OPEN; }
    public void closeDoor() {
        state = upStops.isEmpty() && downStops.isEmpty()
            ? ElevatorState.IDLE
            : (direction == Direction.UP ? ElevatorState.MOVING_UP : ElevatorState.MOVING_DOWN);
    }
}
Java
public interface SchedulingStrategy {
    Elevator selectElevator(List<Elevator> elevators, Request request);
}

// SCAN: pick elevator already moving toward the floor in the same direction
public class ScanStrategy implements SchedulingStrategy {
    @Override
    public Elevator selectElevator(List<Elevator> elevators, Request request) {
        Elevator best = null;
        int minCost = Integer.MAX_VALUE;

        for (Elevator e : elevators) {
            if (e.getState() == ElevatorState.MAINTENANCE) continue;
            int cost = calculateCost(e, request);
            if (cost < minCost) {
                minCost = cost;
                best = e;
            }
        }
        return best;
    }

    private int calculateCost(Elevator e, Request req) {
        int distance = Math.abs(e.getCurrentFloor() - req.getSourceFloor());
        // Prefer elevators moving toward the request in same direction
        if (e.getState() == ElevatorState.IDLE) return distance;
        if (e.getDirection() == req.getDirection()) {
            boolean onTheWay = (req.getDirection() == Direction.UP)
                ? e.getCurrentFloor() <= req.getSourceFloor()
                : e.getCurrentFloor() >= req.getSourceFloor();
            return onTheWay ? distance : distance + 20; // penalty for wrong side
        }
        return distance + 10; // moving opposite direction
    }
}

// Shortest Seek First: pick closest idle or nearest elevator
public class ShortestSeekStrategy implements SchedulingStrategy {
    @Override
    public Elevator selectElevator(List<Elevator> elevators, Request request) {
        return elevators.stream()
            .filter(e -> e.getState() != ElevatorState.MAINTENANCE)
            .min(Comparator.comparingInt(
                e -> Math.abs(e.getCurrentFloor() - request.getSourceFloor())))
            .orElse(null);
    }
}
Java
public class ElevatorSystem {
    private final List<Elevator> elevators;
    private final SchedulingStrategy strategy;

    public ElevatorSystem(int numElevators, SchedulingStrategy strategy) {
        this.strategy = strategy;
        this.elevators = IntStream.range(0, numElevators)
            .mapToObj(Elevator::new)
            .collect(Collectors.toList());
    }

    public void requestElevator(int floor, Direction direction) {
        Request request = new Request(floor, direction);
        Elevator selected = strategy.selectElevator(elevators, request);
        if (selected == null) throw new NoAvailableElevatorException();
        selected.addStop(floor);
    }

    public void selectFloor(int elevatorId, int destinationFloor) {
        Elevator elevator = elevators.get(elevatorId);
        elevator.addStop(destinationFloor);
    }

    // Called by timer thread — drives all elevators forward one step
    public void step() {
        elevators.stream()
            .filter(e -> e.getState() != ElevatorState.IDLE)
            .forEach(Elevator::move);
    }
}

SOLID Principles Applied

Principle How Applied
S — Single Responsibility Elevator manages its own state; SchedulingStrategy handles assignment logic
O — Open/Closed New scheduling algorithms added without modifying ElevatorSystem
L — Liskov Substitution ScanStrategy and ShortestSeekStrategy are interchangeable
I — Interface Segregation SchedulingStrategy has one method — focused interface
D — Dependency Inversion ElevatorSystem depends on SchedulingStrategy interface, not concrete impl

Interview Walkthrough (45 minutes)

Time What to Do
0-5 min Clarify: # floors, # elevators, single vs. multi-building, peak hours
5-15 min Draw class diagram — Elevator, Request, SchedulingStrategy, State Machine
15-25 min Explain SCAN algorithm, show TreeSet-based stop management
25-35 min Code: Elevator.move(), ScanStrategy.selectElevator()
35-45 min Discuss: edge cases (all elevators full), starvation prevention, VIP elevators