Design Projects: Classic Systems · Elevator System

Design an Elevator System

Designing an elevator controller: LOOK scheduling per car, dispatching hall calls, and simulated time.

  • 8 min read
  • 15 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. The pieces in code
  4. Scheduling algorithms
  5. Where you'll see it
  6. Which tool for which job?
  7. Common mistakes
  8. Check yourself
  9. Key takeaways
  10. Quick revise
  11. Practice

Description

An elevator system is a scheduling problem dressed as an object model: cars that move one floor at a time, requests from inside and outside the cars, a rule for which stop comes next, and - with several cars - a dispatcher deciding which car answers each call. It tests whether you can keep the scheduling rule swappable, the safety rules inside the car, and time under the test's control. This skill builds a car that sweeps (LOOK), a dispatcher, and a step-driven simulation.

An elevator looks like a queue of requests, so the first design is one:

Java

class Elevator {
    int floor = 0;
    Queue<Integer> requests = new LinkedList<>();

    void press(int target) { requests.add(target); }

    void run() throws InterruptedException {
        while (true) {
            Integer next = requests.poll();
            if (next == null) { Thread.sleep(100); continue; }
            while (floor != next) { floor += Integer.signum(next - floor); Thread.sleep(1000); }
            System.out.println("Doors open at " + floor);
        }
    }
}

Python

import time
from collections import deque


class Elevator:
    def __init__(self):
        self.floor = 0
        self.requests: deque[int] = deque()

    def press(self, target: int) -> None:
        self.requests.append(target)

    def run(self) -> None:
        while True:
            if not self.requests:
                time.sleep(0.1)
                continue
            target = self.requests.popleft()
            while self.floor != target:
                self.floor += 1 if target > self.floor else -1
                time.sleep(1)
            print(f"Doors open at {self.floor}")

Presses for floors 9, 1, 8 and 2 make it ride 0 → 9 → 1 → 8 → 2: thirty floors, passing every stop twice before serving it. Pressing 7 five times queues five stops at 7. With three cars, the obvious extension - every car answers every call - sends all three to the same floor. And the only way to test any of it is to sleep through it in real time.

We'll go from simple to more technical:

  1. The model: cars, requests, a dispatcher and a step-driven clock.
  2. A car that keeps its direction and stops on the way.
  3. Scheduling algorithms compared: FCFS, nearest-first, SCAN and LOOK.
  4. The bugs, and where safety and concurrency belong.

The approach

  1. Clarify. How many floors and cars? Hall calls (someone on floor 5 presses UP) and car calls (someone inside presses 9) - both? What should be optimized: waiting time, travel, energy? Capacity, express zones, emergency modes - in scope or follow-ups?
  2. Model. A Direction enum (UP, DOWN, IDLE). A Car with its floor, direction and a set of stops. An ElevatorSystem facade that owns the cars and decides which car takes each hall call.
  3. API. requestPickup(floor, direction) returns the assigned car; requestDestination(car, floor); step() advances time by one tick; floorOf, directionOf, stopsServed let tests and displays see the state.
  4. Core. Each car runs LOOK: keep going in the current direction while there's a stop ahead, then turn around. A dispatcher gives each hall call to the nearest car that's idle or already heading that way. Time is a method call, not a clock.

The pieces in code

Java

class Car {
    int floor = 0;
    Direction direction = Direction.IDLE;
    final TreeSet<Integer> stops = new TreeSet<>();      // sorted, no duplicates

    void step() {                                         // one tick
        if (stops.remove(floor)) served.add(floor);       // a stop takes the whole tick
        else if (direction == Direction.UP) floor++;
        else if (direction == Direction.DOWN) floor--;
        updateDirection();
    }

    void updateDirection() {                              // LOOK
        if (stops.isEmpty()) { direction = Direction.IDLE; return; }
        if (stops.contains(floor)) return;                // about to stop here: keep the heading
        Integer above = stops.higher(floor), below = stops.lower(floor);
        if (direction == Direction.UP && above != null) return;
        if (direction == Direction.DOWN && below != null) return;
        if (above != null && below != null)               // idle: nearest first, a tie goes up
            direction = above - floor <= floor - below ? Direction.UP : Direction.DOWN;
        else direction = above != null ? Direction.UP : Direction.DOWN;
    }
}

Python

class Car:
    def __init__(self):
        self.floor = 0
        self.direction = Direction.IDLE
        self.stops: set[int] = set()              # no duplicates
        self.served: list[int] = []

    def step(self) -> None:                       # one tick
        if self.floor in self.stops:              # a stop takes the whole tick
            self.stops.remove(self.floor)
            self.served.append(self.floor)
        elif self.direction is Direction.UP:
            self.floor += 1
        elif self.direction is Direction.DOWN:
            self.floor -= 1
        self.update_direction()

    def update_direction(self) -> None:           # LOOK
        if not self.stops:
            self.direction = Direction.IDLE
            return
        if self.floor in self.stops:
            return                                # about to stop here: keep the heading
        above = min((s for s in self.stops if s > self.floor), default=None)
        below = max((s for s in self.stops if s < self.floor), default=None)
        if self.direction is Direction.UP and above is not None:
            return
        if self.direction is Direction.DOWN and below is not None:
            return
        if above is not None and below is not None:   # idle: nearest first, a tie goes up
            self.direction = Direction.UP if above - self.floor <= self.floor - below else Direction.DOWN
        else:
            self.direction = Direction.UP if above is not None else Direction.DOWN
  • A TreeSet of stops collapses repeated presses and answers "is there a stop above me?" with higher(floor) in O(log n). Python has no sorted set in its standard library: a plain set with min/max scans, as above, is fine for a car's handful of stops, and bisect over a sorted list (or the sortedcontainers package) gives the O(log n) version.
  • step() is the clock. Tests and demos call it; production wraps it in a scheduler that calls it every second. Nothing in the logic sleeps.
  • Direction is recomputed after every change - a new request or a step - in one method, so there's a single place that decides when to turn.

The dispatcher is a cost rule over the cars:

Java

boolean isOnTheWay(Car car, int floor, Direction wanted) {
    if (car.direction == Direction.IDLE) return true;
    if (car.direction != wanted) return false;
    return wanted == Direction.UP ? floor > car.floor : floor < car.floor;
}
// pick the nearest car that is on the way (lowest id on a tie); if none is, the nearest car overall

Python

def is_on_the_way(car: Car, floor: int, wanted: Direction) -> bool:
    if car.direction is Direction.IDLE:
        return True
    if car.direction is not wanted:
        return False
    return floor > car.floor if wanted is Direction.UP else floor < car.floor
# pick the nearest car that is on the way (lowest id on a tie); if none is, the nearest car overall

A car at floor 2 heading up to 15 is the right answer for someone at 6 going up - it passes 6 anyway - even when an idle car is closer, because the idle car would make an extra trip. Someone at 9 going down should wait for a car that will actually be heading down.

Scheduling algorithms

AlgorithmOrderWeakness
FCFSthe order buttons were pressedzigzags - lots of travel
SSTF (shortest seek first)always the nearest pending stopa far floor can starve while nearby requests keep coming
SCANsweep to the end of the shaft, then backtravels to the top even when nobody wants it
LOOKsweep while stops remain ahead, then turnthe usual choice - bounded waiting, little wasted travel

With LOOK, any request is served within two sweeps of the shaft: the car finishes its current direction and comes back. Operating systems schedule disk-head movements with the same four algorithms.

Where you'll see it

Modern high-rises use destination dispatch: riders enter their floor at a lobby panel and are told which car to take, so the dispatcher groups people by destination instead of guessing. Controllers switch traffic modes by time of day (up-peak in the morning sends idle cars back to the lobby), and a separate safety system - not the scheduling code - interlocks the doors and the drive. The dispatcher is also where the interesting money is: the cost function (wait time, ride time, energy) is what vendors compete on.

Which tool for which job?

You needUse
A car's pending stopsa sorted set per car (no duplicate stops)
The next stopLOOK: keep direction while stops lie ahead
Choosing a car for a hall calla dispatcher strategy (on-the-way first, then nearest idle)
Door, moving and maintenance rulesa state machine inside the car
Timea step() method the test or a timer calls
Button presses from many threadsa thread-safe queue drained by one controller thread

Common mistakes

MistakeWhat happensDo this instead
Serving requests in arrival orderthe car zigzags past stops it'll come back forsweep in one direction (LOOK)
Always going to the nearest requesta far floor can wait foreversweep, so every stop is reached within two passes
Every car answering every hall callseveral cars arrive for one personthe dispatcher picks one car
A list of stopsrepeated presses become repeated stopsa set
Sleeping inside the logicslow, timing-dependent testsdrive time through step()

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. A car at floor 0 gets presses for 9, 1, 8 and 2, in that order. How many floors does FCFS travel, and how many does LOOK?

Show answer

FCFS rides 0 → 9 → 1 → 8 → 2: 9 + 8 + 7 + 6 = 30 floors. LOOK (heading up from 0) stops at 1, 2, 8 and 9 on one sweep: 9 floors.

2. Car A is at 2 heading up to 15; car B is idle at 0. Who should take a pickup at floor 6 going up - and one at floor 9 going down?

Show answer

Floor 6 up: car A. It's heading up and 6 is ahead, so it's on the way (4 floors) - better than car B making a separate trip. Floor 9 down: car B. A is nearer but heading up to 15, so it would pass 9 in the wrong direction; the idle car is on the way.

3. Why is step() a method instead of a loop that sleeps for a second per floor?

Show answer

So time is an input. A test can call step() eight times and check where every car is, instantly and the same way every run; a demo can print each tick. In production a timer calls step() once a second - the scheduling logic is identical, only the caller differs.

Key takeaways

  • Model hall calls and car calls, a Car with a sorted set of stops, and one system object that owns the cars.
  • Each car runs LOOK: keep going while stops remain ahead, then turn - no zigzags, no starvation.
  • A dispatcher gives each hall call to one car: the nearest that's idle or already heading that way, with a deterministic tie-break.
  • Drive time through step() so the simulation is deterministic and testable.
  • Safety rules like "never move with the doors open" live inside the car's state, and button presses from many threads go through one queue.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

You needUse
Pending stopsa sorted set per car
The next stopLOOK
A car for a hall calla dispatcher strategy
Safety rulesa state machine in the car
Timestep()
Concurrent buttonsa queue drained by one controller thread
Rush hourstraffic-pattern-aware dispatch

Snippets worth knowing by heart

Java

void step() {
    if (stops.remove(floor)) return;                       // stop here this tick
    if (stops.isEmpty()) return;                           // idle
    boolean ahead = direction > 0 ? stops.higher(floor) != null : stops.lower(floor) != null;
    if (!ahead) direction = -direction;                    // LOOK: turn only when nothing lies ahead
    floor += direction;
}

Python

def step(self) -> None:
    if self.floor in self.stops:
        self.stops.discard(self.floor)                     # stop here this tick
        return
    if not self.stops:
        return                                             # idle
    if not any((s - self.floor) * self.direction > 0 for s in self.stops):
        self.direction = -self.direction                   # LOOK
    self.floor += self.direction

The traps

  • FIFO order.
  • Nearest-first starvation.
  • Every car answering every call.
  • Duplicate stops.
  • Sleeping in the logic.

Staff-level points, one line each

  • Compare schedulers by tail: SSTF and LOOK averaged 13.6 vs 13.9 ticks, but worst waits were 80 vs 46.
  • Up-peak: park idle cars at the lobby; down-peak and lunch need different dispatch.
  • Destination dispatch groups passengers by floor before they board.
  • A car near its load limit shouldn't take on-the-way pickups.
  • Fire recall is a state that overrides everything; door obstruction leads to nudging.
  • One writer: buttons enqueue, the controller drains each tick.

Say it in an interview

  • "Each car sweeps in one direction while there are stops ahead - every request is reached within two sweeps."
  • "The dispatcher prefers a car already heading that way, then the nearest idle one."
  • "Time comes from step(), so tests are instant and deterministic."
  • "I'd evaluate the scheduler by simulation - average and worst-case wait."

Practice

This skill comes with 15 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Design an Elevator System free

Serve the Floors in LOOK Order

EasyFreeCoding problemJava · Python

An elevator runs the LOOK algorithm. Given the floor it's on, the direction it's heading (UP, DOWN or IDLE) and the floors that have been requested, print the order it stops at them and how many floors it travels.

Build an Elevator Controller

MediumProDesign projectJava · Python

Build the controller for a bank of elevators. People call a car from a floor (a hall call: "I'm on 5, going down") or choose a floor from inside a car (a car call).

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Design an Elevator System free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime