Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
🎯 Problem Statement
Design an elevator system for a building that can:- Handle multiple elevators
- Optimize elevator assignment for efficiency
- Support different scheduling algorithms
- Handle concurrent requests from multiple floors
📋 Step 1: Clarify Requirements
Questions to Ask the Interviewer
| Category | Question | Impact on Design |
|---|---|---|
| Scale | How many elevators and floors? | Data structures, concurrency |
| Algorithm | Which scheduling algorithm? | Strategy pattern selection |
| Features | VIP/express elevators? | Additional elevator types |
| Capacity | Weight limits? Max passengers? | Capacity checking logic |
| Emergency | Fire mode? Emergency stop? | State machine complexity |
| UI | Display current floor in elevator? | Observer pattern for updates |
Functional Requirements
- Support N elevators and M floors
- Handle up/down requests from floors (hall buttons)
- Handle floor selection from inside elevator (car buttons)
- Display current floor and direction
- Handle door open/close operations
- Emergency stop functionality
Non-Functional Requirements
- Minimize average wait time
- Minimize travel time (optimize scheduling)
- Handle concurrent requests thread-safely
- Graceful degradation if elevator fails
🧩 Step 2: Identify Core Objects
Hardware
Control
Requests
Entity-Responsibility Mapping
| Entity | Responsibilities | Pattern |
|---|---|---|
Elevator | Move, track floor, manage doors | State Pattern |
Door | Open/close with safety checks | State Pattern |
ElevatorController | Coordinate all elevators | Singleton |
SchedulingStrategy | Assign elevator to request | Strategy Pattern |
FloorDisplay | Show elevator positions | Observer Pattern |
Scheduling Algorithms
| Algorithm | Description | Best For |
|---|---|---|
| FCFS | First come, first served | Simple buildings |
| SCAN | Elevator goes one direction until end | Medium buildings |
| LOOK | Like SCAN but reverses at last request | Most efficient |
| Nearest | Assign closest idle elevator | Low traffic |
📐 Step 3: Class Diagram
Step 4: Implementation
Enums and Constants
Request Classes
Door Class
Elevator Class
Scheduling Strategies
Elevator Controller
Display Panel
Step 5: Usage Example
Scheduling Algorithm Comparison
| Algorithm | Pros | Cons | Best For |
|---|---|---|---|
| FCFS | Simple, fair | Can be inefficient | Low traffic |
| Shortest Seek | Minimizes wait for nearest | Starvation possible | Medium traffic |
| SCAN | No starvation, efficient | Longer max wait | High traffic |
| LOOK | More efficient than SCAN | Complex | High-rise buildings |
Key Design Decisions
Why Strategy Pattern for Scheduling?
Why Strategy Pattern for Scheduling?
Why Two Sorted Lists for Stops?
Why Two Sorted Lists for Stops?
Why Thread Locks?
Why Thread Locks?
Interview Deep-Dive Questions
Compare FCFS, Shortest Seek, SCAN, and LOOK scheduling. When does each one fail?
Compare FCFS, Shortest Seek, SCAN, and LOOK scheduling. When does each one fail?
- FCFS is the naive baseline: requests are served in arrival order. It fails badly in a 40-floor building because an elevator on floor 1 might get dispatched to floor 38, then floor 2, then floor 37 — constant full-length traversals. Average wait time degrades linearly with building height.
- Shortest Seek (nearest first) minimizes immediate wait but causes starvation. If a steady stream of requests arrives near floor 5, a person waiting on floor 30 may never get served. This is the classic “shortest job first starvation” problem from OS scheduling.
- SCAN (elevator algorithm) sweeps fully in one direction then reverses. It guarantees bounded wait time (at most 2 full sweeps) but wastes time traveling to the physical top/bottom even when no requests exist above/below.
- LOOK fixes SCAN’s waste by reversing at the last request in each direction, not at the physical boundary. It is the standard real-world choice for most buildings. Its failure mode is bunching under asymmetric load: if floors 1-5 generate 80% of traffic (lobby, cafeteria, gym), LOOK spends most of its time in that range and upper floors see degraded service.
- The real-world answer is rarely a single algorithm. Modern systems like Otis Compass use destination dispatch — passengers enter their destination floor before boarding, and the controller groups passengers going to similar floors into the same elevator. This converts the problem from scheduling to batching and reduces stops per trip by 30-50%.
- Example: In a hospital, you might use FCFS for fairness (emergency floors should not be starved), while in a commercial high-rise you would use LOOK with zone-based partitioning (elevators 1-3 serve floors 1-20, elevators 4-6 serve floors 21-40).
- How would you modify LOOK to handle the asymmetric load problem where one floor (like the lobby at 9 AM) generates 10x the traffic of all other floors combined?
- What metrics would you instrument in production to decide whether to switch scheduling algorithms — and could you make the switch at runtime?
The Elevator class uses separate _up_stops and _down_stops sorted lists. Why not a single sorted set?
The Elevator class uses separate _up_stops and _down_stops sorted lists. Why not a single sorted set?
- The two-list design mirrors how the SCAN/LOOK algorithm thinks about the world: “what do I need to service in my current direction, and what is waiting for the return trip?” With a single sorted set, every time the elevator needs its next stop, it must filter the entire set by comparing each floor against the current floor and direction. That is O(n) per lookup instead of O(1).
- With two sorted lists, the next stop going up is always
_up_stops[0](minimum element) and the next stop going down is always_down_stops[-1](maximum element). Both are O(1) lookups on a sorted container. This is the key performance insight. - The trade-off is complexity in the add_destination logic. When the elevator is going UP and someone inside presses a floor below the current floor, that floor goes into
_down_stopsand will be serviced on the return trip. If you get this assignment wrong, you introduce bugs where floors are never visited or visited in the wrong sweep. - There is a subtle correctness issue in the current code:
add_destinationdecides up vs. down based solely on whether the floor is above or belowcurrent_floor, ignoring the current direction. If the elevator is on floor 5 going UP and someone requests floor 3, it correctly goes into_down_stops. But if someone requests floor 7 while the elevator is going DOWN, it goes into_up_stops— which is correct for the next sweep but means it will not be served until the elevator finishes its downward sweep and reverses. Whether this is a bug or a feature depends on your scheduling semantics. - Example: Think of it like a printer’s duplex mode. Pages to print on the forward pass go in one queue, pages for the reverse pass go in another. Mixing them into one queue would require sorting logic on every page.
- What happens if the elevator is at floor 10 going DOWN with
_down_stops = [7, 3], and a new request comes in for floor 8 going UP? Which list does floor 8 go into, and when does the passenger get picked up? - If you needed to support priority requests (e.g., VIP floors that jump the queue), how would you modify the two-list data structure?
The Door class uses time.sleep() to simulate opening and closing. What is wrong with this in a concurrent system?
The Door class uses time.sleep() to simulate opening and closing. What is wrong with this in a concurrent system?
time.sleep()inside a lock is a concurrency killer. The Door’sopen()method holdsself._lockand then sleeps for 0.5 seconds. During that sleep, any other thread trying to callopen()orclose()on the same door is blocked — which is fine for a single door. But the real problem is upstream: theElevator._stop()method callsdoor.open(), sleeps forDOOR_OPERATION_TIME(2 seconds), then callsdoor.close(). During those 3 total seconds of sleep, the elevator’s thread is completely blocked.- In the
ElevatorController.run()loop, each elevator’smove()is called sequentially. If elevator 0 stops and opens its door, the entire controller loop stalls for 3 seconds. Elevators 1 and 2 cannot move during that time — passengers on other elevators literally freeze in place while one elevator loads passengers. - The fix is event-driven or async architecture. Replace
time.sleep()with state transitions and timers. The door has states CLOSED -> OPENING -> OPEN -> CLOSING -> CLOSED, and each transition is triggered by a timer callback, not a blocking sleep. The elevator run loop checksif door.state == OPEN and time_since_opened > DOOR_TIMEOUT: door.close()on each tick without blocking. - Alternatively, run each elevator in its own thread (the code almost does this but does not). Then door operations on elevator 0 only block elevator 0’s thread. But this introduces coordination complexity when multiple elevators need to make decisions based on shared state (like pending requests).
- Example: Real elevator controllers use a PLC (programmable logic controller) running a scan cycle. Each cycle reads all inputs (buttons, sensors), evaluates state machines, and sets outputs (motor direction, door commands). Nothing ever blocks — it is a pure event loop running at 10-50ms cycle time.
asyncio without explaining how it would integrate with the existing thread-based design.Follow-ups:- If you switch to an event-driven model, how do you handle the edge case where a passenger holds the door open (interrupting the CLOSING -> CLOSED transition)?
- Redesign the
Elevator.move()andElevator._stop()methods to be non-blocking, using only state checks and time comparisons instead of sleep.
The SCANScheduler scores idle elevators at distance*1 and same-direction elevators at distance*0.5. Derive why 0.5 is wrong and what the score should be.
The SCANScheduler scores idle elevators at distance*1 and same-direction elevators at distance*0.5. Derive why 0.5 is wrong and what the score should be.
- The 0.5 multiplier says “an elevator already moving toward you is twice as good as an idle one at the same distance.” But that ignores a critical factor: the elevator’s existing stop queue. An elevator going UP at floor 3 with stops at floors 5, 8, and 12 is NOT equivalent to an idle elevator at floor 3, because it will stop three times before potentially reaching your floor.
- The correct score for a same-direction elevator should factor in the number of intermediate stops and the time cost per stop (door open + dwell + door close, roughly 5-8 seconds each). A better formula:
score = distance + (num_intermediate_stops * STOP_PENALTY)where intermediate stops are destinations between the elevator’s current floor and the request floor. - For an opposite-direction elevator, the score should account for the full remaining travel in the current direction, the reversal, and then the distance to the request. It is not simply
distance * 2— it isdistance_to_last_stop_in_current_direction + distance_from_that_stop_to_request_floor + (num_remaining_stops * STOP_PENALTY). - For idle elevators, the score is simply
distancesince there are no intermediate stops and no direction overhead. - The 0.5 magic number also fails at boundary conditions. An idle elevator 1 floor away scores 1.0, but an elevator 10 floors away moving toward you scores 5.0. The idle elevator is clearly better, and the scoring correctly reflects that. But an idle elevator 10 floors away scores 10.0, while a same-direction elevator 8 floors away with zero intermediate stops scores 4.0 — now the moving elevator wins, but only because we ignored the fact it might pick up new passengers at intermediate floors.
- Example: The Destination Dispatch systems (ThyssenKrupp TWIN, Otis Compass) use estimated time of arrival (ETA) as the score, not distance. ETA accounts for travel speed, acceleration/deceleration profiles, door operation times, and passenger loading estimates.
- How would you estimate the STOP_PENALTY dynamically instead of using a constant — for example, during rush hour when more passengers board at each stop?
- If you compute ETA-based scores, that computation takes time. At what point does the scoring overhead itself become a bottleneck, and how would you approximate it?
How would you add emergency fire mode where all elevators return to the ground floor and stop?
How would you add emergency fire mode where all elevators return to the ground floor and stop?
- Fire mode is a global state transition that overrides all normal behavior. The key design insight is that it is not just “go to floor 1” — there are strict safety sequencing requirements mandated by fire codes (ASME A17.1 in the US).
- Phase 1 (Recall): On fire alarm signal, all elevators cancel their current destinations, ignore all new button presses, close doors immediately (overriding door-hold buttons), and travel non-stop to the designated recall floor (usually lobby, but can be alternate if fire is on the lobby floor). Elevators must not stop at any intermediate floor, even if passengers press buttons.
- Phase 2 (Firefighter Operation): Once recalled, elevators lock out normal operation. Only a physical key switch allows a firefighter to manually control one elevator with limited functionality (no automatic door close — doors only close when the firefighter holds the button, preventing them from being trapped).
- Implementation: Add an
EMERGENCYstate toElevatorState. In theElevatorController, add atrigger_fire_mode(fire_floor: int)method that (1) sets a globalfire_mode = Trueflag, (2) determines the recall floor (lobby unless fire is at lobby, then use alternate), (3) for each elevator: clears_up_stopsand_down_stops, force-closes doors, adds recall floor as sole destination, transitions state to EMERGENCY. Themove()method checksif self._state == EMERGENCY and self._current_floor == recall_floor: stop and disable. - The critical edge case is an elevator that is between floors when the alarm triggers. It must complete travel to the nearest floor (you cannot stop between floors — passengers would be trapped), open doors briefly to allow evacuation, then proceed to recall. The current
move()implementation moves one floor at a time, so this is handled naturally, but you need to suppress the normal stop logic during emergency recall. - Example: The 2001 WTC evacuation revealed issues with elevator recall when fire was in the lobby. Modern codes require alternate recall floors, and many buildings now have dedicated firefighter elevators with independent power supplies and pressurized shafts.
- How do you handle an elevator that has a mechanical fault and cannot move during fire recall — does it affect the other elevators’ behavior?
- After the fire alarm is cleared, how do you transition back to normal operation? Can you just flip
fire_mode = False, or are there intermediate states needed?
You have 6 elevators in a 40-floor building. How would you partition them to minimize average wait time during morning rush?
You have 6 elevators in a 40-floor building. How would you partition them to minimize average wait time during morning rush?
- Morning rush has a distinctive traffic pattern: almost all trips originate at the lobby (floor 1) going UP, and the destination distribution depends on the building’s tenant layout. This is called up-peak traffic and it is the hardest pattern for a naive algorithm because all elevators cluster at the lobby and compete for the same pool of passengers.
- Zone-based partitioning: Divide the building into zones (floors 1-13, 14-26, 27-40) and assign 2 elevators per zone. Each elevator only stops within its zone. This converts 6 elevators doing 40-floor sweeps into 3 pairs doing 13-floor sweeps — average trip time drops roughly 3x. The trade-off is that inter-zone travel requires transferring at a sky lobby, and low-demand zones still have 2 elevators even if they only need 1.
- Dynamic zoning: During morning rush, assign 4 elevators to lobby express mode (they only serve the lobby for pickup and go directly to each passenger’s floor) and keep 2 for inter-floor travel. After rush hour, switch to normal LOOK scheduling. This requires time-of-day awareness and smooth transitions.
- Destination dispatch (the modern answer): Instead of up/down buttons, passengers enter their destination floor at the lobby terminal. The controller batches passengers going to adjacent floors into the same elevator. Elevator A takes floors 8, 9, 10; elevator B takes floors 22, 23, 25. Each elevator makes 2-3 stops instead of 10-15. ThyssenKrupp claims 30% reduction in travel time with this approach.
- The math insight: With 6 elevators and 40 floors, the theoretical minimum average wait time during up-peak is
round_trip_time / (2 * num_elevators). Round trip time = travel_time_up + travel_time_down + (average_stops * stop_penalty) + loading_time. The only variables you can control are average_stops (via destination dispatch) and partitioning (via zoning). - Example: The Burj Khalifa uses a sky lobby system with zone-based partitioning. Passengers take a shuttle elevator to the sky lobby, then transfer to a local elevator for their zone. This is the only practical approach for buildings over 60 floors.
- At 5 PM the traffic pattern reverses — everyone goes DOWN to the lobby. Does your morning rush optimization hurt the evening rush, and how would you handle the transition?
- What data would you collect during the first month of building operation to calibrate your partitioning, and how often would you re-calibrate?
The ElevatorController directly sets elevator._state when toggling maintenance. What SOLID principle does this violate, and what is the proper state machine design?
The ElevatorController directly sets elevator._state when toggling maintenance. What SOLID principle does this violate, and what is the proper state machine design?
- Setting
elevator._statedirectly from outside the class violates encapsulation and the Single Responsibility Principle. The controller is reaching into the elevator’s internals and mutating private state (note the underscore prefix convention). The Elevator class should own its own state transitions and enforce valid transitions — for example, you should not be able to go from MOVING to MAINTENANCE without first stopping. - The proper design is a state machine with explicit transition rules. Define a transition table: STOPPED -> MAINTENANCE (valid), MOVING -> MAINTENANCE (invalid — must stop first), MAINTENANCE -> STOPPED (valid, after inspection). The Elevator class exposes
enter_maintenance()andexit_maintenance()methods that validate the transition internally and raise an error if the transition is not allowed. - A full state machine for an elevator has more states than the current 3. A production model includes: IDLE, MOVING_UP, MOVING_DOWN, STOPPING, DOORS_OPENING, DOORS_OPEN, DOORS_CLOSING, EMERGENCY_RECALL, MAINTENANCE, OUT_OF_SERVICE. Each state has a defined set of valid transitions and associated actions.
- Implementation approach: Use the State pattern — each state is a class with methods like
on_floor_request(),on_timer_tick(),on_door_sensor(). The elevator holds a reference to its current state object and delegates behavior. When the state changes, you swap the state object. This eliminates the giant if/else chains that inevitably grow as you add states. - The State pattern also makes it trivial to add the emergency mode: create an
EmergencyStateclass that ignores button presses, overrides door behavior, and only transitions toIdleStatewhen the emergency is cleared. - Example: The State pattern is exactly how vending machines, ATMs, and traffic lights are modeled in embedded systems. Each state object knows its valid transitions and refuses invalid ones, making it impossible to reach an undefined state through a programming error.
- If the elevator is currently between floors (MOVING state) and the maintenance team needs to take it offline urgently, what is the safe sequence of state transitions?
- How would you log and audit all state transitions for regulatory compliance, and how does the State pattern make that easier than the current if/else approach?
The can_serve method determines if an elevator can 'efficiently' serve a request. Walk me through a scenario where it makes the wrong decision.
The can_serve method determines if an elevator can 'efficiently' serve a request. Walk me through a scenario where it makes the wrong decision.
can_servereturns True only if the elevator is idle OR if it is moving in the request’s direction with the request floor ahead of it. This is too conservative and misses a common optimization case.- Scenario: Elevator is on floor 8 going UP with
_up_stops = [10, 15]. A request comes in from floor 12 going DOWN.can_servereturns False because the elevator is going UP and the request direction is DOWN. So the controller assigns a different elevator, possibly one much further away. But elevator 0 will pass through floor 12 on its way to 15. It could stop at 12, pick up the passenger, and add their DOWN destination to_down_stopsfor the return trip. This is exactly what the SCAN algorithm should do, butcan_serveprevents it. - The root issue is that
can_serveconflates two things: “can the elevator pick up this passenger?” and “can the elevator immediately serve their direction?” In SCAN, these are different. The elevator can pick up a DOWN-requesting passenger on the way up, as long as the passenger understands they will ride up to 15 first and then come back down. - However, this introduces a UX question: do passengers accept riding past their pickup floor in the “wrong” direction? In some systems (like destination dispatch), yes. In traditional systems with hall lanterns showing direction, no — the UP arrow is lit, and DOWN-bound passengers would not board.
- A more nuanced
can_servewould return a score rather than a boolean: “this elevator can serve this request with an estimated detour of X floors.” The scheduler then compares detour costs across all elevators and picks the lowest cost, even if it means a slight detour. - Example: Imagine a 3-elevator building where elevator A is going UP past the request floor, elevator B is idle 20 floors away, and elevator C is in maintenance. The current
can_servewould reject A and send B on a 20-floor trip, when A could have served the request with zero additional travel.
- If you allow the elevator to pick up a DOWN-requesting passenger while going UP, how do you update the hall lantern (direction indicator) to avoid confusing passengers who should not board?
- How would you modify
can_serveto return a cost instead of a boolean, and what factors would you include in the cost function?