How to Design an Elevator System: The Complete Interview Walkthrough

May 27, 202612 min read
interview-prepcareerdsaalgorithms
How to Design an Elevator System: The Complete Interview Walkthrough
TL;DR
  • The elevator system design interview tests scheduling logic, not OOP. The data model takes five minutes; the dispatch algorithm is where you earn the hire signal.
  • Use the LOOK algorithm: reverse at the last requested floor in each direction, not at the physical end. Two sorted sets per elevator make the next-stop lookup O(1).
  • For multi-elevator dispatch, use a direction-aware cost function that adds a large penalty to cars moving away from or in the wrong direction relative to the request.
  • Destination dispatch groups passengers by destination before boarding, cutting stops per trip 30-50% in high-traffic buildings compared to standard hall-call dispatch.
  • Model fault tolerance with controller heartbeats every 5 seconds: mark a car OUT_OF_SERVICE after three missed beats and redistribute its pending stops.
  • Zone partitioning (floors 1-20 to cars 1-3, floors 21-40 to cars 4-6) handles scale before you need distributed systems or Kafka.
  • Structure 45 minutes as: clarification, three-tier architecture, data model and state machine, LOOK plus cost function, fault tolerance, destination dispatch tradeoffs.

Most candidates walk into the elevator system design interview and immediately open a class diagram: Elevator, Floor, Button, Door. Five minutes later they have a beautiful UML sketch and zero scheduling logic. The interviewer stares at it. They've seen this exact sketch 40 times this quarter.

The insight that unlocks this problem: an elevator system is a scheduling problem wearing an OOP costume. The data model takes five minutes to sketch. The dispatch algorithm is where you actually earn the hire signal.

A real elevator controller running .NET Framework with an unhandled exception dialog: "HRESULT E_FAIL has been returned from a call to a COM component." Building info reads "Aufzug 4" (Elevator 4), capacity 20 persons.

An actual elevator running .NET Framework, photographed in the wild. HRESULT E_FAIL returned from a COM component. The dispatch algorithm was, by all accounts, not dispatching.


Clarify Before You Draw Anything

This is an unusual system design question because the scope bifurcates hard depending on the building.

Ask these four things first:

  • How many elevators, how many floors? A 10-floor office with 3 cars is a single-controller problem. A 100-floor skyscraper with 30 cars is a group-control problem with zone partitioning.
  • Is this a standard elevator (up/down hall call) or a destination dispatch system? Destination dispatch, where you enter your destination floor before boarding, changes the algorithm entirely.
  • What do we optimize for? Average wait time, average ride time, or worst-case starvation? These pull in opposite directions.
  • Any special cars? Express elevators, freight elevators, VIP floors, emergency lockout. Each is an exception to the base algorithm.

For a typical interview, anchor on: 1 building, 3 to 6 elevators, 20 to 30 floors, standard up/down hall calls, optimize for average wait time. That's the canonical problem. Write it down so the interviewer sees you've scoped it deliberately.


Three Boxes, One Hard Boundary

The system has three logical layers.

Floor Panels send hall calls (up or down button pressed on floor N) to the central dispatcher. They also receive floor indicator signals back, showing which car is coming and roughly when.

Central Dispatcher holds the scheduling brain. It knows every elevator's position, direction, and pending stop list. When a hall call arrives, it assigns it to the best car and pushes the new stop into that car's queue.

Elevator Controllers are embedded systems on each car. Each one executes the movement loop: open doors, close doors, move to next target floor, repeat. The controller reports position and state back to the dispatcher via events.

The dispatcher is centralized; the motor control is distributed. You don't want the network path in the loop that decides when to hold the door. That's a safety hazard. Push the scheduling logic to the dispatcher, keep the real-time actuation local.

Three-tier elevator architecture diagram: Floor Panels on the left sending hall calls and receiving ETA updates from the Central Dispatcher in the center, which runs the LOOK scheduler and pushes stop assignments to Elevator Controllers on the right. Controllers report position and state events back.

The hard boundary sits between dispatcher and controller. Scheduling crosses the wire; motor control never does.


The Data Model Takes Five Minutes

Four entities cover the domain.

from enum import Enum from dataclasses import dataclass, field from typing import Optional class Direction(Enum): UP = "UP" DOWN = "DOWN" IDLE = "IDLE" class ElevatorState(Enum): IDLE = "IDLE" MOVING = "MOVING" DOORS_OPEN = "DOORS_OPEN" OUT_OF_SERVICE = "OUT_OF_SERVICE" @dataclass class Request: floor: int direction: Optional[Direction] = None # None = internal (button inside car) @dataclass class Elevator: id: int current_floor: int = 1 direction: Direction = Direction.IDLE state: ElevatorState = ElevatorState.IDLE up_stops: set = field(default_factory=set) # floors to visit going up down_stops: set = field(default_factory=set) # floors to visit going down capacity: int = 10 passengers: int = 0

Two sorted sets per elevator, one for up requests and one for down requests. That data structure choice is the entire LOOK algorithm in disguise, and we'll come back to it.

The state machine has four states and eight transitions. The interesting part is not the state machine itself.

Elevator state machine diagram showing four states: IDLE transitions to MOVING when a call is received; MOVING transitions to DOORS_OPEN on reaching a floor; DOORS_OPEN returns to MOVING when the door timer expires with pending stops, or to IDLE when no stops remain. Any state can transition to OUT_OF_SERVICE on three missed heartbeats (shown as dashed red lines).

The OUT_OF_SERVICE transitions (dashed) fire whenever the dispatcher misses three consecutive heartbeats from a car. Everything else is just a door timer.

It is what goes into up_stops and down_stops and when.


The Scheduling Algorithm: LOOK Beats SCAN

Most candidates stall here. They know the elevator moves. They're not sure which floors to visit in what order.

There are four classical algorithms worth knowing.

FCFS (First Come First Serve): serve requests in arrival order. Floor 1, then floor 15, then floor 3. The elevator zigzags across the building like it's trying to find its keys, and everyone waits forever. Never use this.

SSTF (Shortest Seek Time First): always go to the closest requested floor next. Efficient in the short run, but floors at the extremes can starve permanently. In a busy building where everyone needs the lobby, the penthouse passenger just keeps getting skipped. Indefinitely.

SCAN: move in one direction, serve every requested floor along the way, go all the way to the top floor, reverse, go all the way to the bottom, repeat. Fair, no starvation, but it wastes time traveling to the end even when the last request sits several floors short of it.

LOOK: like SCAN, but the elevator reverses at the last requested floor in the current direction, not at the physical end. This is what real elevator systems use. No wasted trips to empty extremes, no starvation, simple to implement.

def next_stop(elevator: Elevator) -> Optional[int]: if elevator.direction == Direction.UP: # all up-stops above current floor candidates = [f for f in elevator.up_stops if f >= elevator.current_floor] if candidates: return min(candidates) # no more up-stops: flip direction, drain down-stops candidates = [f for f in elevator.down_stops if f <= elevator.current_floor] return max(candidates) if candidates else None if elevator.direction == Direction.DOWN: candidates = [f for f in elevator.down_stops if f <= elevator.current_floor] if candidates: return max(candidates) candidates = [f for f in elevator.up_stops if f >= elevator.current_floor] return min(candidates) if candidates else None # IDLE: take either set all_stops = elevator.up_stops | elevator.down_stops return min(all_stops, key=lambda f: abs(f - elevator.current_floor)) if all_stops else None

The two-set structure makes this O(1) for the common case: just peek at the min of up_stops or max of down_stops. No sorting required on every step.

SCAN vs LOOK comparison on a vertical floor strip. Both elevators start at floor 8 going UP with requests at floors 5, 10, 15, and 22. SCAN travels all the way to floor 30 before reversing (wasted trip shown in red box). LOOK reverses at floor 22, the last actual request, saving 8 floors of empty travel.

SCAN's wasted trip highlighted in red. Nobody is on floor 30. Nobody asked to go to floor 30. The elevator goes anyway.


Multi-Elevator Dispatch: Which Car Gets the Call?

With multiple elevators, the dispatcher runs a cost function on every hall call to pick the best car.

The naive approach is nearest car: send the hall call to the elevator closest in floor distance. Simple, usually fine, but it ignores direction entirely. Sending floor 10's UP call to an elevator currently at floor 12 going UP means that car is already past the request and moving away. The passenger waits for it to finish its entire upward run, reverse, and come all the way back down.

A better cost function adds a direction penalty. Assign zero penalty to an elevator already moving toward the request in the correct direction, a medium penalty to an idle elevator, and a large penalty to one moving away.

def cost(elevator: Elevator, request: Request) -> int: distance = abs(elevator.current_floor - request.floor) if elevator.state == ElevatorState.OUT_OF_SERVICE: return float('inf') if elevator.direction == Direction.IDLE: return distance if elevator.direction == request.direction: # moving toward the request in the right direction if (elevator.direction == Direction.UP and elevator.current_floor <= request.floor) or \ (elevator.direction == Direction.DOWN and elevator.current_floor >= request.floor): return distance # moving away or wrong direction: add large penalty return distance + 100

Dispatch cost comparison diagram: a vertical floor strip with Car A at floor 12 going UP and Car B at floor 5 going UP. A hall call arrives at floor 10 UP. Car A cost panel shows distance 2, but it's moving away, so cost = 2 + 100 = 102. Car B cost panel shows distance 5, moving toward the request, cost = 5. Car B wins despite being farther away.

Nearest car picks Car A (2 floors away) and assigns a call that car will arrive at in roughly forever. Direction-aware cost picks Car B.

This gets you 80% of the way to production behavior. Modern systems go further with destination dispatch: passengers enter their destination floor on a lobby keypad before boarding. The dispatcher now groups passengers with identical destinations into the same car, dramatically reducing stops per trip. ThyssenKrupp's AGILE system and Kone's destination control both work this way. Wait times in high-traffic buildings drop 30 to 50% compared to standard hall-call dispatch. The assignment problem is similar in structure to ticketing seat reservation: avoid assigning the same slot to two people, handle the case where the best option fills up mid-assignment.


What Breaks and How You Fix It

Peak-hour thundering herd. At 9 AM in a 50-floor office building, hundreds of people call elevators simultaneously. The dispatcher queues up hall calls and processes them in order, but a single-threaded dispatcher can lag under that burst. The fix: process assignments asynchronously and buffer incoming calls. Assignments are idempotent, so a retry on timeout is safe.

Elevator failure mid-trip. The controller sends heartbeats every 5 seconds. If the dispatcher misses three in a row, it marks the car OUT_OF_SERVICE and redistributes its pending stops to remaining cars. Think of it as checking on a coworker: one missed message is fine, two is a little suspicious, three and you're sending someone to their desk. Passengers inside a stuck car hit the emergency button, which bypasses the dispatcher entirely and connects to the building's emergency line.

Network partition between dispatcher and controller. Each controller holds its current stop list locally. If it loses the dispatcher connection, it finishes its current stop list, holds at the next floor with doors open, and waits for reconnection. It does not attempt to accept new hall calls. Safety over liveness.


The Calls That Define Your Design

Every one of these is a push-versus-pull style call where neither side is universally right. If you want a framework for reasoning through them, The Tradeoff Maze covers the same decision structure.

DecisionOption AOption BPick when
DispatchNearest carDirection-aware costAlways use cost; nearest is only a fallback
AlgorithmSCANLOOKAlways LOOK; SCAN wastes trips
Hall callsAssign immediatelyQueue centrally, assign lazilyImmediate is simpler and reduces wait display lag
Destination dispatchStandard up/downPre-board destination entryHigh-traffic buildings; adds kiosk hardware cost
Controller autonomyFully centralizedLocal fallback on disconnectAlways add local fallback for safety

Elevator System Design Interview: The 45-Minute Clock

  • 0-5: Clarify (building size, number of cars, optimization target, special floors)
  • 5-12: Sketch the three-tier architecture (floor panels, dispatcher, controllers)
  • 12-22: Data model and state machine (Elevator entity, two-set stop structure)
  • 22-33: LOOK algorithm walkthrough, multi-elevator cost function, where requests get inserted
  • 33-40: Fault tolerance (heartbeat, redistribution, local fallback)
  • 40-45: Destination dispatch as the scaling upgrade, tradeoffs table

If the interviewer pushes on scale, add zone partitioning (floors 1-20 assigned to cars 1-3, floors 21-40 to cars 4-6) before jumping to distributed systems. This is still a single building. You do not need Kafka.


Recap

  • Clarify building size and optimization target before touching the whiteboard. The algorithm changes based on answers.
  • Elevator system design is a scheduling problem. The OOP layer is 10 minutes of work.
  • Use LOOK, not SCAN. Two sorted sets per elevator. Reverse at last request, not at the physical end.
  • Multi-elevator dispatch: direction-aware cost function, not nearest-floor.
  • Destination dispatch groups passengers by destination, cutting stops per trip by 30-50% in high-traffic buildings.
  • Fault tolerance: heartbeat from controller, redistribution on miss, local fallback on disconnect.
  • Zone partitioning handles scale before you need distributed systems.

Explaining your algorithm choices out loud, under time pressure, is the actual skill being tested. SpaceComplexity runs voice-based mock system design interviews with rubric feedback on exactly this: not just whether your architecture is correct, but whether you can communicate the tradeoffs clearly to a real interviewer.


Further Reading