BackendMedium35 min

Design an Elevator System (LLD)

Model cars, floors and requests as a state machine, then pick a dispatch strategy that keeps waits short and never starves a floor.

Say it in 30 seconds
  • 2 sweepslongest wait bound under LOOK
  • ~148 sround-trip time in the 20-floor example
  • 72 s vs 52 stime stopping versus travelling per trip
Show what to say

“I'd split the problem in two: dispatch decides which car answers a hall call, and scheduling decides the order a car serves its stops. Each car is a state machine running LOOK over up-stops and down-stops, so no floor waits more than two sweeps. One controller owns every hall call and gives it to exactly one car through a pluggable strategy.”

Trade-offs

  • FCFS vs nearest car: FCFS is trivial and fair by arrival but zig-zags; direction-aware nearest car gives short waits but commits at press time.
  • Nearest car vs collective: collective keeps no assignment state and suits small groups, but cars bunch and calls may wait.
  • One controller vs none: no double service of a call, but a single point of failure, so cars fall back to local mode.

Bring it up before they ask

When a car fails or goes out of service, its hall calls move to other cars keeping their queue place, and riders exit at the next floor.

0 of 0 sections ticked
30-second answer

Each car is a state machine that serves stops in LOOK order, so no floor waits more than two sweeps. One controller gives each hall call to exactly one car through a swappable dispatch strategy.

Functional

  • N floors, M cars; ▲/▼ buttons on each floor, a panel in each car
  • One car answers each hall call, stopping in the requested direction
  • Doors open at stops; a sensor reopens them
  • Cars show floor and direction; hall lamps stay lit until answered
  • A full car skips hall calls; an overloaded car will not leave
  • Out-of-service and fire-service modes

Non-functional

  • Short average wait, bounded longest wait
  • Each hall call goes to exactly one car, even under simultaneous presses
  • Doors open only when stopped at a floor; never move with doors open
  • Deterministic tests: time is an injected tick
  • New dispatch strategies plug in without touching the car

Out of scope

  • Motor drives and acceleration profiles
  • Destination dispatch kiosks (a follow-up)
  • Many buildings (an HLD follow-up)

A low-level design (LLD) question with two problems inside. Dispatch: which car answers a call. Scheduling: in what order a car serves its stops.

New to this? Read Swap behaviour and State machines and observers first.

Clarify, interface and state

Ask first:

Read more · 117 words
  • How many floors and cars?
  • An office with a morning rush, a hotel, a hospital?
  • ▲/▼ buttons, or a keypad in the lobby?
  • What happens in a car failure or fire alarm?
  • Shortest average wait, or shortest longest wait?

The interface

CallReturnsNotes
hallCall(floor, dir)Car id, or null if queuedIdempotent: a lit button stays one call
carCall(carId, floor)Adds a stop to that car
tick()Advances every car one step; tests drive it
takeOutOfService(carId)Its hall calls move to other cars
returnToService(carId)Back in the dispatch pool
events assigned, queued, cleared, arrivedLamps, displays and logs observe these

Each car exposes addStop(floor, dir?), step() and openDoors(), which throws while moving.

Build the design in stages

Step through: building, then requests, then dispatch as a strategy, then the displays. Green marks what the latest stage added.

Read more · 110 words

Do the sum on the building before adding a layer:

BuildingWhat it needs
1 car, 5 floorsA queue in press order
1 car, 20 floorsLOOK, or it zig-zags
4 cars, 20 floorsDispatch, one owner per call

Monday 9 am, 120 people press ▲ in the lobby within five minutes. Two cars with no single owner both answer 5▲, and one rides empty.

What it remembers. All of it fits in the controller's memory. This is one instant.

  • A hall call is in exactly one place: assigned (to one car) or waiting.
  • Up calls go to upStops, down calls to downStops.
  • After a restart, panels re-send lit calls.

Stage 1 of 5

The building

ElevatorSystem owns the floors and the cars. Each floor has a hall panel with the up and down buttons.

Elevator system class diagramElevatorSystem owns the floors, the cars and every hall call. A DispatchStrategy (NearestCar or Collective) picks which car answers; each ElevatorCar owns its door and panel and orders its own stops with LOOK. Displays observe the system's events.HallPanelfloor: numberup, down: lamppress(dir)clear(dir)Floorlevel: numberpanel: HallPanelElevatorSystemcars: ElevatorCar[]assigned: Map<key, car>waiting: HallCall[]strategyhallCall(floor, dir)carCall(carId, floor)tick()takeOutOfService(carId)ElevatorCarfloor, dir, stateupStops, downStops: SetdoorTimer: ticksaddStop(floor, dir?)step(): Stop | nullopenDoors()takeOutOfService()1..*hallCall()1..*

ElevatorSystem owns the floors, the cars and every hall call. A DispatchStrategy (NearestCar or Collective) picks which car answers; each ElevatorCar owns its door and panel and orders its own stops with LOOK. Displays observe the system's events.

  • ◆ composition (owns)
  • ◇ aggregation
  • △ inheritance / implements (dashed)
  • → association · ⇢ dependency
// the controller's state at one instant
const state = {
  cars: [
    { id: 'A', floor: 3, dir: 'up', state: 'MOVING_UP', upStops: new Set([5, 7]), downStops: new Set([2]) },
    { id: 'B', floor: 0, dir: null, state: 'IDLE', upStops: new Set(), downStops: new Set() },
  ],
  assigned: new Map([['5▲', { call: { floor: 5, dir: 'up' }, carId: 'A' }]]), // owned exactly when it is here
  waiting: [{ floor: 9, dir: 'down', key: '9▼' }], // no car can take it yet, oldest first
};

Watch it run

Three cars, eight floors. Press ▲ or ▼ to add a rider.

Read more · 85 words

Switch Strategy to replay the same riders, and take car B out of service mid-trip. After five simulated minutes:

  • Random traffic: first-come-first-served (FCFS) waits nearly twice as long (about 8 s against 4–5 s).
  • Morning rush: FCFS rides run about 30% longer (14 s against 11 s).
  • Nearest car and Collective are close.

Hall call to doors open, step by step. Play it, then try all cars busy (the call waits, retried each tick) and car B fails (the call gets a new owner).

A queue is a line at a ticket window: people wait in order and are served one at a time.
Interactive

Lift dispatch simulator

Three cars, eight floors. Press ▲ or ▼ on a floor to add a person.

G1234567AGIDLEB3IDLEC6IDLE
Sim time
0:00
Average wait
0.0 s
Longest wait
0.0 s
Average ride
0.0 s
Delivered
0
Floors travelled
0

Collective (LOOK), random traffic. Press a ▲ or ▼ button to add a person.

More controls

Press Play to watch it run · 9 steps

Someone on floor 5 presses ▲. The controller checks the key is new and asks the strategy for a car, which adds a stop. Arriving on its way up, the car opens, the lamp clears, and the rider presses 12. Switch branches to see every car busy, or the chosen car failing.

A hall call, from button to doorsSomeone on floor 5 presses ▲. The controller checks the key is new and asks the strategy for a car, which adds a stop. Arriving on its way up, the car opens, the lamp clears, and the rider presses 12. Switch branches to see every car busy, or the chosen car failing.Someone on floor 5 presses ▲ (twice: the second press is ignored)hallCall(5, up)key 5▲ is newpick(5▲, cars in service)alt [car assigned]car B (cost 3)addStop(5, up)lamp 5▲ onB steps floor by floor; LOOK keeps its sweep while stops lie aheadarrived(5, up)cleared 5▲: lamp offDoors open only now: B is stopped exactly at 5carCall(12)Sensor clear, timer done: doors close, B continues up to 12
  • Client
  • Service
  • Worker
Text description
  1. Hall panel 5 → ElevatorSystem: hallCall(5, up)
  2. ElevatorSystem: key 5▲ is new
  3. ElevatorSystem → Strategy: pick(5▲, cars in service)
  4. Strategy replies to ElevatorSystem: car B (cost 3)
  5. ElevatorSystem → Car B: addStop(5, up)
  6. ElevatorSystem replies to Hall panel 5: lamp 5▲ on
  7. Car B sends (async) to ElevatorSystem: arrived(5, up)
  8. ElevatorSystem sends (async) to Hall panel 5: cleared 5▲: lamp off
  9. Car B panel → Car B: carCall(12)

Step-by-step answer

Order: clarify, split dispatch from scheduling, draw one car's state machine, pick and defend strategies (LOOK per car, nearest car across cars), then walk a scenario and break it (double press, full car, failed car).

Read more · 330 words
  1. Use cases and nouns

    Call, ride, doors, display, failures. Nouns: ElevatorSystem, ElevatorCar, HallCall, CarCall, DispatchStrategy.

  2. One car is a state machine

    FromEventTo
    IDLEa stop on another floorMOVING_UP or MOVING_DOWN
    IDLEa call at this floorDOORS_OPEN
    MOVING_UP / MOVING_DOWNreached a floor it must stop atIDLE, then DOORS_OPEN
    DOORS_OPENtimer done, sensor clearIDLE
    anyfault or maintenance keyOUT_OF_SERVICE

    Safety lives in the car: openDoors() throws while moving, and step() never moves with doors open.

  3. Two sets of stops per car

    LOOK asks: is any stop ahead, and is this floor a stop in my direction?

  4. LOOK, not FCFS or SCAN

    • FCFS: press order, zig-zagging.
    • Nearest stop first: can starve a far floor.
    • LOOK: sweeps up, then down, and turns at the last request.

    A car never turns while a stop lies ahead, so every floor is served within two sweeps.

  5. Dispatch: which car answers

    Nearest car picks the lowest cost, following each car's sweep, plus a penalty per stop on the way.

    // car at 2 sweeping up to 5; call 4▼: up to 5, back to 4 = 4 floors, plus 2 for the stop at 5
    cost = routeDistance(car, call.floor, call.dir) + stopCost * stopsBefore;

    Collective assigns nothing up front. A call waits for a car passing its way. Simpler, but cars bunch.

  6. One owner per call

    Presses are messages to one controller loop. The first 5▲ is assigned. The second finds it in assigned and does nothing. No locks.

  7. Doors, sensor and load

    A blocked sensor reopens the doors. Over ~80% load the car skips hall calls.

  8. Failures and modes

    A failed car hands its calls to others. Fire service only changes which cars dispatch can use.

  9. Displays and lamps

    Displays and lamps listen for arrived and cleared (an observer).

  10. Tests

    Inject the tick. Test LOOK order and the double press. Check after every tick: no open doors in motion, one owner per call.

Implementation

The same design in JavaScript, Java or Python. Java and Python add real locks.

Read more · 64 words
  • #plan() is all of LOOK: stop here if it is a stop in the sweep direction, go on while anything lies ahead, else turn.
  • routeDistance: a car at 2 sweeping up to 5 is 2 floors from 4▲, but 6 from 4▼.

Code it yourself. Try the Elevator Scheduler exercise: one car, a tick at a time, with LOOK and first-come-first-served as swappable policies.

export const Dir = Object.freeze({ UP: 'up', DOWN: 'down' });

export const CarState = Object.freeze({
  IDLE: 'IDLE',                     // stopped at a floor, doors closed
  MOVING_UP: 'MOVING_UP',
  MOVING_DOWN: 'MOVING_DOWN',
  DOORS_OPEN: 'DOORS_OPEN',         // only ever entered while stopped at a floor
  OUT_OF_SERVICE: 'OUT_OF_SERVICE',
});

// A hall call is a floor plus the arrow someone pressed; a car call is a floor pressed inside one car.
export const hallCall = (floor, dir) => ({ kind: 'hall', floor, dir, key: `${floor}${dir === Dir.UP ? '▲' : '▼'}` });
export const carCall = (carId, floor) => ({ kind: 'car', carId, floor });

export const sign = dir => (dir === Dir.UP ? 1 : -1);
export const opposite = dir => (dir === Dir.UP ? Dir.DOWN : Dir.UP);

Capacity estimate

A follow-up: how many cars does a building need?

Read more · 175 words

Size for the up-peak, the morning rush when every trip starts in the lobby. One car's round-trip time (RTT) sets everything.

QuantityEstimate
Building20 floors, 3.5 m each
People50 per floor = 1,000; 12% in the busiest 5 minutes = 120
Load per trip13-person car, 80% full: P = 10
Travel2.5 m/s: 1.4 s per floor
Stops per tripS ≈ 8 (10 riders, some share a floor)
Time per stop~8 s (doors and braking)
Transfer1.2 s per passenger

RTT = travel 52 + stops 72 + transfers 24 ≈ 148 s.

  • One car: 300 × 10 ÷ 148 ≈ 20 people per 5 minutes.
  • Four cars: about 81, one leaving the lobby every ~37 s (offices aim for under 30 s).
  • Six cars: about 121, every ~25 s.

The surprise: stopping (72 s) costs more than travelling (52 s). Destination dispatch halves the stops to 4, so four cars then move about 103. The controller is tiny (0.4 presses a second), so correctness is the hard part. The timeline plays one trip.

One car's trip, second by second0 s7.5 s15.0 s22.6 s30.1 s37.6 sMotionG → 5G → 5: 0–10 s5 → 125 → 12: 17.4–30.2 sDoorsopen: 10–12 scloseclose: 14.4–17.4 sopen: 30.2–32.2 scloseclose: 34.6–37.6 sPassengers2 board: 12–14.4 s2 leave: 32.2–34.6 s5▲ pressed5▲ clearedleaves 5at 12

An idle car at G answers 5▲ and carries two people to 12, using the capacity numbers: 1.4 s per floor, 3 s lost braking and accelerating per leg, doors 2 s to open and 3 s to close, 1.2 s per person. Of 37.6 s, only 16.8 s is full-speed travel; the rest is spent stopping.

Text description
  • Motion: G → 5 (0–10 s), 5 → 12 (17.4–30.2 s)
  • Doors: open (10–12 s), close (14.4–17.4 s), open (30.2–32.2 s), close (34.6–37.6 s)
  • Passengers: 2 board (12–14.4 s), 2 leave (32.2–34.6 s)

Failure modes and trade-offs

Read more · 173 words
What breaksEffectMitigation
Doors blockedCar stuckSensor reopens; a slow close after ~20 s, then a fault
A car failsIts calls wait foreverCalls go to other cars; riders exit at the next floor
Power cutCar stops between floorsBrakes hold; battery drives it to the nearest floor
Sensor faultCar loses its positionStop at the next floor, go out of service
OverloadUnsafe to leaveNo departure over capacity
Controller crashAssignments lostPanels re-send; cars serve calls alone
Fire alarmLifts are dangerousRecall to the designated floor, doors open

Trade-offs

ChoiceProsCons
FCFSTrivial and fairZig-zags; long waits
Nearest carShort waitsCommits at press time
CollectiveNo state to keepCars bunch
Destination dispatchFewer stopsNeeds keypads
One controllerNo double serviceSingle point of failure

Follow-up questions

How would destination dispatch change the design?

Riders key in their floor, so HallCall becomes a DestinationRequest (from, to) behind the same strategy. Grouping by floor cuts stops.

How would you add VIP and fire-service modes?

Modes are system state. A VIP key pulls one car out of dispatch. Fire recall parks every car with doors open. LOOK is untouched.

How would you scale this to many buildings?

Keep dispatch local, since safety can't depend on a network. Stream events to a central service, split by building, for dashboards.

How would you save energy or prepare for the morning rush?

Park idle cars by policy (the lobby before 9 am). Add an energy cost so a moving car beats waking a parked one.

How do you test it?

Inject the tick and check the rules over seeded random runs. Replaying one seed compares strategies fairly.

Check yourself

Question 1 of 3

Score 2 out of 3 or better to mark this page complete.

1. Car A is at floor 2, sweeping up to a stop at 5. Car B is idle at 6. Who should answer 4▼?