On this page
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:
- The model: cars, requests, a dispatcher and a step-driven clock.
- A car that keeps its direction and stops on the way.
- Scheduling algorithms compared: FCFS, nearest-first, SCAN and LOOK.
- The bugs, and where safety and concurrency belong.
The approach
- 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?
- Model. A
Directionenum (UP,DOWN,IDLE). ACarwith its floor, direction and a set of stops. AnElevatorSystemfacade that owns the cars and decides which car takes each hall call. - API.
requestPickup(floor, direction)returns the assigned car;requestDestination(car, floor);step()advances time by one tick;floorOf,directionOf,stopsServedlet tests and displays see the state. - 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
TreeSetof stops collapses repeated presses and answers "is there a stop above me?" withhigher(floor)in O(log n). Python has no sorted set in its standard library: a plainsetwithmin/maxscans, as above, is fine for a car's handful of stops, andbisectover a sorted list (or thesortedcontainerspackage) 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
| Algorithm | Order | Weakness |
|---|---|---|
| FCFS | the order buttons were pressed | zigzags - lots of travel |
| SSTF (shortest seek first) | always the nearest pending stop | a far floor can starve while nearby requests keep coming |
| SCAN | sweep to the end of the shaft, then back | travels to the top even when nobody wants it |
| LOOK | sweep while stops remain ahead, then turn | the 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 need | Use |
|---|---|
| A car's pending stops | a sorted set per car (no duplicate stops) |
| The next stop | LOOK: keep direction while stops lie ahead |
| Choosing a car for a hall call | a dispatcher strategy (on-the-way first, then nearest idle) |
| Door, moving and maintenance rules | a state machine inside the car |
| Time | a step() method the test or a timer calls |
| Button presses from many threads | a thread-safe queue drained by one controller thread |
Common mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
| Serving requests in arrival order | the car zigzags past stops it'll come back for | sweep in one direction (LOOK) |
| Always going to the nearest request | a far floor can wait forever | sweep, so every stop is reached within two passes |
| Every car answering every hall call | several cars arrive for one person | the dispatcher picks one car |
| A list of stops | repeated presses become repeated stops | a set |
| Sleeping inside the logic | slow, timing-dependent tests | drive 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
Carwith 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.