Skip to main content
Scenario Article

LLD for Elevator System: Designing a Smart Lift

The Hotel Lift Problem: What We Are Designing

Abstract Algorithmsβ€’Mar 9, 2026
XLinkedInReddit
πŸ’‘ Executive Summary

A hotel has 10 floors and 3 elevators.

🎯 System Scenario & Requirements

System Goal

A hotel has 10 floors and 3 elevators.

Actors & Components

  • Application or API tier
  • Core service under design
  • Operator or SRE observer

Functional Requirements

  • A hotel has 10 floors and 3 elevators.
  • At any moment: - External requests come in: "Someone on Floor 5 pressed UP." - Internal requests come in: "Someone inside Lift 1 pressed Floor 10." - The system must decide which car to dispatch, and which order to serv…
  • This is a classic State Machine + Scheduling design.

Constraints & Assumptions

⚑ Execution Stages & Decision Trace

1

Stage 1 β€” Elevator System Basics: Entities, States, and Button Types

Action / Decision:

Before writing a single line of code it helps to name every moving part in plain English.

Component Behavior:

Core entities: States an ElevatorCar can be in: - IDLE β€” the car is stopped and has no pending stops.

Observable Output: - MOVING UP β€” the car is travelling upward and serving stops along the way.
Read Full Stage in Deep Dive ↓
πŸ“–

Deep Dive

Detailed Architectural Analysis

Scenario context

A hotel has 10 floors and 3 elevators. At any moment:

  • External requests come in: "Someone on Floor 5 pressed UP."
  • Internal requests come in: "Someone inside Lift 1 pressed Floor 10."
  • The system must decide which car to dispatch, and which order to serve floors.

This is a classic State Machine + Scheduling design. The key insight is that a lift behaves differently based on its current state β€” and we should…

This walkthrough focuses on The Hotel Lift Problem: What We Are Designing.

Before making a design choice, clarify:

  • A hotel has 10 floors and 3 elevators.
  • At any moment: - External requests come in: "Someone on Floor 5 pressed UP." - Internal requests come in: "Someone inside Lift 1 pressed Floor 10." - The system must decide which car to dispatch, and which order to serv…
  • This is a classic State Machine + Scheduling design.

Concepts in play: low-level-design-systems, jvm-internals-and-garbage-collection, design-patterns-gof, design-principles-solid

Stage 1 β€” Elevator System Basics: Entities, States, and Button Types

  • Before writing a single line of code it helps to name every moving part in plain English.
  • Core entities: States an ElevatorCar can be in: - IDLE β€” the car is stopped and has no pending stops.
    • MOVING UP β€” the car is travelling upward and serving stops along the way.
    • MOVING DOWN β€” the car is travelling downward and serving stops along the way.

Observable outcome: - MOVING DOWN β€” the car is travelling downward and serving stops along the way.

Stage 2 β€” Domain Model: The Core Entities

    • ElevatorCar: Holds a sorted set of destination floors.
  • Has a state (IDLE, MOVING UP, MOVING DOWN).
  • Assigns incoming Request to the best car.
    • Request: Either ExternalRequest (floor + direction pressed outside) or InternalRequest (destination floor pressed inside the car).
classDiagram
    class Building {
        +List~Floor~ floors
        +List~ElevatorCar~ cars
        +Dispatcher dispatcher
        +handleExternalRequest(Request r)
    }
    class ElevatorCar {
        +int id
        +int currentFloor
        +Direction direction
        +ElevatorState state
        -TreeSet~Integer~ stops
        +addStop(int floor)
        +move()
        +getCurrentFloor() int
    }
    class Dispatcher {
        -DispatchStrategy strategy
        +assign(Request r) ElevatorCar
    }
    class Request {
        +int sourceFloor
        +Direction direction
    }
    class ElevatorState {
        <>
        +handleExternalRequest(ElevatorCar car, Request r)
        +handleInternalRequest(ElevatorCar car, int floor)
        +isIdle() bool
    }
    class DispatchStrategy {
        <>
        +assign(Request request, List~ElevatorCar~ available) ElevatorCar
    }
    class IdleState {
        +handleExternalRequest(ElevatorCar, Request)
        +handleInternalRequest(ElevatorCar, int)
        +isIdle() bool
    }
    class MovingUpState {
        +handleExternalRequest(ElevatorCar, Request)
        +handleInternalRequest(ElevatorCar, int)
        +isIdle() bool
    }
    class MovingDownState {
        +handleExternalRequest(ElevatorCar, Request)
        +handleInternalRequest(ElevatorCar, int)
        +isIdle() bool
    }
    class NearestCarStrategy {
        +assign(Request, List~ElevatorCar~) ElevatorCar
    }

    Building "1" *-- "many" ElevatorCar : contains
    Building --> Dispatcher : uses
    Dispatcher o-- DispatchStrategy : uses (injected)
    ElevatorCar o-- ElevatorState : state (State Pattern)
    ElevatorState <|.. IdleState : implements
    ElevatorState <|.. MovingUpState : implements
    ElevatorState <|.. MovingDownState : implements
    DispatchStrategy <|.. NearestCarStrategy : implements
    Dispatcher --> ElevatorCar : assigns

Observable outcome: - Request: Either ExternalRequest (floor + direction pressed outside) or InternalRequest (destination floor pressed inside the car).

Stage 3 β€” Deep Dive: State Pattern for Elevator Movement

  • An elevator's behavior depends on its current state.
  • We make this explicit with the State pattern: The state transitions:
public interface ElevatorState {
    void handleRequest(ElevatorCar car, int targetFloor);
    String name();
}

public class IdleState implements ElevatorState {
    public void handleRequest(ElevatorCar car, int targetFloor) {
        car.addStop(targetFloor);
        if (targetFloor > car.getCurrentFloor()) {
            car.setState(new MovingUpState());
        } else if (targetFloor < car.getCurrentFloor()) {
            car.setState(new MovingDownState());
        }
    }
    public String name() { return "IDLE"; }
}

public class MovingUpState implements ElevatorState {
    public void handleRequest(ElevatorCar car, int targetFloor) {
        // SCAN: only add if it's above current floor
        if (targetFloor >= car.getCurrentFloor()) {
            car.addStop(targetFloor);   // serve on the way
        } else {
            car.deferRequest(targetFloor);  // serve on the next sweep
        }
    }
    public String name() { return "MOVING_UP"; }
}
stateDiagram-v2
    [*] --> IDLE
    IDLE --> MOVING_UP : request above current floor
    IDLE --> MOVING_DOWN : request below current floor
    MOVING_UP --> IDLE : no more up requests
    MOVING_UP --> MOVING_DOWN : switch at top
    MOVING_DOWN --> IDLE : no more down requests
    MOVING_DOWN --> MOVING_UP : switch at bottom

Observable outcome: We make this explicit with the State pattern: The state transitions:

Stage 4 β€” Dispatcher: Strategy Pattern for Assignment Policy

  • The Dispatcher uses the Strategy pattern so we can swap assignment algorithms: canService() checks the car's state: - IDLE β†’ always serviceable.
    • MOVING UP β†’ serviceable if request is above current floor and direction matches.
    • MOVING DOWN β†’ serviceable if request is below current floor and direction matches.
public interface DispatchStrategy {
    ElevatorCar select(List<ElevatorCar> cars, Request request);
}

// Nearest-Car strategy: pick the car that will reach the requester soonest
public class NearestCarStrategy implements DispatchStrategy {
    public ElevatorCar select(List<ElevatorCar> cars, Request request) {
        return cars.stream()
            .filter(c -> c.canService(request))
            .min(Comparator.comparingInt(c ->
                Math.abs(c.getCurrentFloor() - request.getSourceFloor())))
            .orElse(null);
    }
}

Observable outcome: - MOVING DOWN β†’ serviceable if request is below current floor and direction matches.

Stage 5 β€” Interface Contracts: DispatchStrategy and ElevatorState

  • Two interfaces form the entire behavioral backbone of this design.
  • Every concrete class β€” state implementations, dispatch algorithms, the Dispatcher itself β€” is wired together through these two contracts.
  • Why separate handleExternalRequest from handleInternalRequest ?
  • External requests originate from a floor panel and route through the Dispatcher β€” the strategy can load-balance across cars.
// Who implements: NearestCarStrategy, ZoneStrategy, LookStrategy
// Who consumes:   Dispatcher
interface DispatchStrategy {
    ElevatorCar assign(Request request, List<ElevatorCar> available);
}

// Who implements: IdleState, MovingUpState, MovingDownState
// Who consumes:   ElevatorCar.handleExternalRequest(), ElevatorCar.handleInternalRequest()
interface ElevatorState {
    void handleExternalRequest(ElevatorCar car, Request request);
    void handleInternalRequest(ElevatorCar car, int targetFloor);
    boolean isIdle();
}
sequenceDiagram
    participant U as User (Floor 3)
    participant D as Dispatcher
    participant S as DispatchStrategy
    participant C as ElevatorCar
    participant ST as ElevatorState
    U->>D: handleExternalRequest(floor=3, UP)
    D->>S: assign(request, availableCars)
    S->>S: score each car by distance
    S-->>D: bestCar (Car #2)
    D->>C: handleExternalRequest(car, request)
    C->>ST: delegate to current state
    ST->>C: addStop(floor=3)
    ST->>C: setState(MovingUpState)
    C-->>D: stop added
    Note over C: Car moves toward Floor 3 via SCAN

Observable outcome: External requests originate from a floor panel and route through the Dispatcher β€” the strategy can load-balance across cars.

Stage 6 β€” OOP Pillars in the Elevator Design

  • Each of the four OOP pillars maps cleanly to a specific boundary in this design.
  • The table below names the pillar, the class or interface that embodies it, and the concrete mechanism.
  • Encapsulation deep-dive: The TreeSet<Integer inside ElevatorCar is the most critical encapsulation boundary in the design.
  • If external code could add floors to the set arbitrarily, it could insert a below-current-floor stop while the car is sweeping up β€” silently breaking the SCAN invariant.
classDiagram
    class Building {
        +List~Floor~ floors
        +List~ElevatorCar~ cars
        +Dispatcher dispatcher
        +handleExternalRequest(Request) void
    }
    class ElevatorCar {
        +int id
        +int currentFloor
        -ElevatorState state
        -TreeSet~Integer~ stopQueue
        +addStop(int) void
        +deferRequest(int) void
        +move() void
        +setState(ElevatorState) void
        +getCurrentFloor() int
    }
    class Dispatcher {
        -DispatchStrategy strategy
        +assign(Request, List~ElevatorCar~) ElevatorCar
    }
    class ElevatorState {
        <>
        +handleExternalRequest(ElevatorCar, Request) void
        +handleInternalRequest(ElevatorCar, int) void
        +isIdle() boolean
    }
    class DispatchStrategy {
        <>
        +assign(Request, List~ElevatorCar~) ElevatorCar
    }
    class IdleState {
        +handleExternalRequest(ElevatorCar, Request) void
        +handleInternalRequest(ElevatorCar, int) void
        +isIdle() boolean
    }
    class MovingUpState {
        +handleExternalRequest(ElevatorCar, Request) void
        +handleInternalRequest(ElevatorCar, int) void
        +isIdle() boolean
    }
    class MovingDownState {
        +handleExternalRequest(ElevatorCar, Request) void
        +handleInternalRequest(ElevatorCar, int) void
        +isIdle() boolean
    }
    class NearestCarStrategy {
        +assign(Request, List~ElevatorCar~) ElevatorCar
    }
    class DestinationDispatchStrategy {
        +assign(Request, List~ElevatorCar~) ElevatorCar
    }
    class Request {
        +int sourceFloor
        +Direction direction
    }
    class Direction {
        <>
        UP
        DOWN
    }
    Building "1" *-- "many" ElevatorCar : contains
    Building --> Dispatcher : uses
    Dispatcher o-- DispatchStrategy : uses (injected)
    ElevatorCar o-- ElevatorState : current state
    ElevatorState <|.. IdleState : implements
    ElevatorState <|.. MovingUpState : implements
    ElevatorState <|.. MovingDownState : implements
    DispatchStrategy <|.. NearestCarStrategy : implements
    DispatchStrategy <|.. DestinationDispatchStrategy : implements
    Dispatcher --> ElevatorCar : assigns
    Request --> Direction : uses

Observable outcome: If external code could add floors to the set arbitrarily, it could insert a below-current-floor stop while the car is sweeping up β€” silently breaking the SCAN invariant.

End-to-end outcome

A hotel has 10 floors and 3 elevators.

Alternatives considered

Decision Guide: Choosing the Right Elevator Scheduling Strategy

Start with the SCAN algorithm and Nearest-Car dispatch.

Failure and degraded modes

Trade-offs & Failure Modes in Elevator Design

  • Detection: Trade-offs & Failure Modes in Elevator Design
  • User or operator impact: Trade-offs & Failure Modes in Elevator Design
First article in seriesNext Article β†’

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit