Case Studies

Elevator System

Design an elevator control system with multiple cars, floor requests, and scheduling.

3 min readDesigns · LLD & Patterns

Interview practice: try designing this yourself before reading. Go to the exercise ↓

Problem Statement

Design an elevator system for a building that:

  • Has multiple elevator cars and many floors
  • Handles internal requests (from inside a car) and external requests (from a floor)
  • Schedules cars efficiently (minimize wait + travel time)
  • Supports up/down buttons, emergency stop, weight limit

Requirements

Functional

  • N cars × M floors
  • External request: (floor, direction)
  • Internal request: (car, floor)
  • Pick the best car for each external request
  • Open/close doors safely
  • Emergency stop & alarm

Non-Functional

  • Concurrent requests from multiple users
  • Real-time scheduling
  • Fault tolerance — a stuck car shouldn't block the system

High-Level Architecture


Class Diagram


Core Classes

public enum Direction { UP, DOWN, IDLE }
public enum CarState { MOVING, STOPPED, DOOR_OPEN, MAINTENANCE }

public class Request {
    public final int floor;
    public final Direction direction;
    public final long timestamp;

    public Request(int floor, Direction d) {
        this.floor = floor;
        this.direction = d;
        this.timestamp = System.currentTimeMillis();
    }
}

ElevatorCar

public class ElevatorCar {
    private final int id;
    private int currentFloor = 0;
    private Direction direction = Direction.IDLE;
    private CarState state = CarState.STOPPED;

    // SCAN/LOOK algorithm: separate up/down stop queues
    private final TreeSet<Integer> upStops = new TreeSet<>();
    private final TreeSet<Integer> downStops = new TreeSet<>(Comparator.reverseOrder());

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

    public synchronized void step() {
        if (direction == Direction.UP || direction == Direction.IDLE) {
            if (!upStops.isEmpty()) {
                direction = Direction.UP;
                int next = upStops.first();
                if (currentFloor == next) {
                    upStops.pollFirst();
                    openDoor();
                } else {
                    currentFloor++;
                }
                return;
            }
        }
        if (!downStops.isEmpty()) {
            direction = Direction.DOWN;
            int next = downStops.first();
            if (currentFloor == next) {
                downStops.pollFirst();
                openDoor();
            } else {
                currentFloor--;
            }
            return;
        }
        direction = Direction.IDLE;
    }

    private void openDoor() {
        state = CarState.DOOR_OPEN;
        // schedule close after delay
    }

    public int getCurrentFloor() { return currentFloor; }
    public Direction getDirection() { return direction; }
    public int getId() { return id; }

    public ElevatorCar(int id) { this.id = id; }
}

Dispatching Strategy (Strategy Pattern)

public interface Dispatcher {
    ElevatorCar pickCar(Request req, List<ElevatorCar> cars);
}

public class NearestCarDispatcher implements Dispatcher {
    @Override
    public ElevatorCar pickCar(Request req, List<ElevatorCar> cars) {
        return cars.stream()
            .filter(c -> isCompatible(c, req))
            .min(Comparator.comparingInt(c -> Math.abs(c.getCurrentFloor() - req.floor)))
            .orElse(cars.get(0));
    }

    private boolean isCompatible(ElevatorCar c, Request r) {
        if (c.getDirection() == Direction.IDLE) return true;
        // Going up, request is above the car, also going up
        if (c.getDirection() == Direction.UP && r.direction == Direction.UP
            && r.floor >= c.getCurrentFloor()) return true;
        if (c.getDirection() == Direction.DOWN && r.direction == Direction.DOWN
            && r.floor <= c.getCurrentFloor()) return true;
        return false;
    }
}

Other strategies: LOOK (current direction first, then reverse), SCAN, fixed-sectoring (each car serves a floor band).


ElevatorSystem (Singleton or DI'd)

public class ElevatorSystem {
    private final List<ElevatorCar> cars;
    private final Dispatcher dispatcher;
    private final ScheduledExecutorService ticker;

    public ElevatorSystem(int numCars, Dispatcher d) {
        this.cars = IntStream.range(0, numCars)
            .mapToObj(ElevatorCar::new)
            .collect(Collectors.toList());
        this.dispatcher = d;
        this.ticker = Executors.newSingleThreadScheduledExecutor();
        ticker.scheduleAtFixedRate(this::tick, 0, 1, TimeUnit.SECONDS);
    }

    public void externalRequest(int floor, Direction dir) {
        Request req = new Request(floor, dir);
        ElevatorCar car = dispatcher.pickCar(req, cars);
        car.addStop(floor);
    }

    public void internalRequest(int carId, int floor) {
        cars.get(carId).addStop(floor);
    }

    private void tick() {
        cars.forEach(ElevatorCar::step);
    }
}

A single ticker thread advances all cars 1 floor per second. Real systems use per-car physical sensors.


Concurrency

Shared resourceProtection
Each car's up/down queuessynchronized on ElevatorCar
The car listImmutable after construction
Dispatcher decisionsStateless

For multiple buildings, give each its own ElevatorSystem — no shared state.


Sequence: External Request


Edge Cases

CaseHandling
All cars in maintenanceReject request, show "out of service"
Weight limit exceededDon't move; alarm
Door obstructedRe-open, retry
Power outageMove to nearest floor, open doors
Emergency stopHalt, send to ground floor
Rapid same-floor pressesDebounce

Design Patterns Used

PatternWhere
StrategyDispatching algorithm (NearestCar, LOOK, fixed-sector)
StateCar state (Moving, Stopped, DoorOpen, Maintenance)
SingletonElevatorSystem (per building)
ObserverFloor display boards subscribe to car positions
CommandEach Request as a queueable command

Interview Tips

  • Lead with clarifying questions: how many cars, how many floors, peak vs idle, can cars skip floors?
  • Sketch the class diagram, then walk through the SCAN/LOOK algorithm — it shows you've thought about real elevator scheduling.
  • Discuss tradeoffs of dispatching strategies — fixed-sectoring is simpler but worse during off-peak.
  • Mention morning rush vs evening rush as example workloads requiring different scheduling.

Practice

Try it yourself

Design an elevator controller for a building with several elevator cars.

Your design should cover

  • Hall calls (up or down at a floor) and car calls (floor buttons inside a car)
  • Dispatch each hall call to a suitable car
  • Cars serve requests efficiently and do not starve any floor
  • Door, direction and idle states per car
  • Swap the dispatching algorithm without rewriting the system
0 / 8,000 characters · saved in this browser

Finished reading?

Mark it done to track your progress in Designs.