Machine Coding Problem

Elevator System

maco30maco60macoAllinfrastructurestatestrategy-(dispatching)
Commonly Asked By:GoogleAmazonMicrosoftUber

Requirements & System Scope

Functional Scope (In-Scope)

  • N Floors & M Elevators: Support customizable scale, controlling multiple distinct elevator cabins traversing standard floor heights.
  • Dual-Request Ingestion: Process external hall calls (floor + direction button pressed outside) and internal cabin calls (target floor button pressed inside).
  • Minimized Wait Times: Swappable dispatch strategies to minimize elevator distance and passenger wait cycles.
  • Dynamic Stop Sorting (SCAN): Cabins should process floors efficiently on their path (e.g. stopping for floor 3 when moving from 0 to 5) before switching direction.

Explicit Boundaries (Out-of-Scope)

  • No Weight Sensors / Hardware Alarms: Omit physical cabin components (brakes, cable tension, overload warning lights).
  • No Interactive Floor Displays: UI feedback is aggregated as status prints in the controller event tick loop.

Class Diagram & Entity Relationships

Model layout showcasing state representation and strategy abstraction:

Loading...
  • Encapsulated State Machine: Each Elevator cabin encapsulates its internal state (IDLE, MOVING_UP, MOVING_DOWN) and private request sorting collections.
  • Strategy Separation: The DispatchStrategy interface is isolated, letting the controller swap dispatch algorithms at runtime.

Design Patterns & SOLID Principles

  • State Pattern (Elevator Operations): Instead of scattering fragile nested if/else checks across cabin operations, the active cabin state transitions are isolated to clean enums guarding logical operations (e.g. cannot open doors while moving).
  • Strategy Pattern (Request Dispatching): Dispatching algorithms (Nearest-Idle, FCFS, or SCAN-Optimized) implement a single interface, making the allocation logic completely pluggable.

Core Execution Workflows

1. Hall Request Entry Workflow

  1. Passenger presses UP on Floor 3.
  2. An external Request is generated and pushed to ElevatorController.
  3. Controller triggers DispatchStrategy.selectElevator() to resolve the optimal cabin.
  4. The selected Elevator cabin appends Floor 3 to its internal sorted requests pool.
  5. The cabin shifts state from IDLE to MOVING_UP and starts its travel ticks.

2. SCAN Cabin Traversal Tick

  1. Every tick step, the active cabin moves one floor closer to its current pathway direction.
  2. Cabin checks if currentFloor matches the nearest entry in its active destination set.
  3. Upon matching, the cabin halts, purges the floor entry from the active queue, and opens doors (logs arrival).
  4. When the active direction requests are completely empty, the cabin checks if opposite direction requests exist to pivot, or resets to IDLE.

Concurrency & Thread Safety Strategy

In high-rise settings, multiple hall calls are triggered concurrently from different floors, alongside inside cabin selections. Race conditions must be strictly prevented:

  • Cabin Lock Synchronization: Mutex locks (e.g. synchronized methods in Java or Lock in Python) protect the cabin's destination sets (upRequests/downRequests) during concurrent insertions.
  • Concurrent Collections: If the controller aggregates system metrics across cabin statuses, it should query thread-safe collections to avoid runtime exceptions.

Complete Clean Code Blueprint

Clean reference designs showcasing operational patterns and thread-safety in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.*;

enum Direction { UP, DOWN, IDLE }

class Request {
    private final int targetFloor;
    private final Direction direction;

    public Request(int targetFloor, Direction direction) {
        this.targetFloor = targetFloor;
        this.direction = direction;
    }
    public int getTargetFloor() { return targetFloor; }
    public Direction getDirection() { return direction; }
}

class Elevator {
    private final int id;
    private int currentFloor = 0;
    private Direction direction = Direction.IDLE;
    private final TreeSet<Integer> upRequests = new TreeSet<>();
    private final TreeSet<Integer> downRequests = new TreeSet<>();

    public Elevator(int id) {
        this.id = id;
    }

    public int getId() { return id; }
    public synchronized int getCurrentFloor() { return currentFloor; }
    public synchronized Direction getDirection() { return direction; }

    public synchronized void addRequest(int floor) {
        if (floor > currentFloor) {
            upRequests.add(floor);
            if (direction == Direction.IDLE) {
                direction = Direction.UP;
            }
        } else if (floor < currentFloor) {
            downRequests.add(floor);
            if (direction == Direction.IDLE) {
                direction = Direction.DOWN;
            }
        } else {
            System.out.printf("[Elevator %d] Already at floor %d. Doors open.\n", id, floor);
        }
    }

    public synchronized void step() {
        if (direction == Direction.UP) {
            if (!upRequests.isEmpty()) {
                currentFloor++;
                System.out.printf("[Elevator %d] Moving UP: reached floor %d\n", id, currentFloor);
                if (upRequests.contains(currentFloor)) {
                    upRequests.remove(currentFloor);
                    System.out.printf("[Elevator %d] STOPPED at floor %d. Doors open/close.\n", id, currentFloor);
                }
                if (upRequests.isEmpty()) {
                    direction = downRequests.isEmpty() ? Direction.IDLE : Direction.DOWN;
                }
            } else {
                direction = downRequests.isEmpty() ? Direction.IDLE : Direction.DOWN;
            }
        } else if (direction == Direction.DOWN) {
            if (!downRequests.isEmpty()) {
                currentFloor--;
                System.out.printf("[Elevator %d] Moving DOWN: reached floor %d\n", id, currentFloor);
                if (downRequests.contains(currentFloor)) {
                    downRequests.remove(currentFloor);
                    System.out.printf("[Elevator %d] STOPPED at floor %d. Doors open/close.\n", id, currentFloor);
                }
                if (downRequests.isEmpty()) {
                    direction = upRequests.isEmpty() ? Direction.IDLE : Direction.UP;
                }
            } else {
                direction = upRequests.isEmpty() ? Direction.IDLE : Direction.UP;
            }
        }
    }

    public synchronized boolean hasRequests() {
        return !upRequests.isEmpty() || !downRequests.isEmpty();
    }
}

interface DispatchStrategy {
    Elevator selectElevator(List<Elevator> elevators, Request request);
}

class OptimalDispatchStrategy implements DispatchStrategy {
    @Override
    public Elevator selectElevator(List<Elevator> elevators, Request request) {
        Elevator best = null;
        int minCost = Integer.MAX_VALUE;

        for (Elevator e : elevators) {
            int cost = calculateCost(e, request);
            if (cost < minCost) {
                minCost = cost;
                best = e;
            }
        }
        return best != null ? best : elevators.get(0);
    }

    private int calculateCost(Elevator e, Request r) {
        int currentFloor = e.getCurrentFloor();
        Direction dir = e.getDirection();
        int target = r.getTargetFloor();

        if (dir == Direction.IDLE) {
            return Math.abs(currentFloor - target);
        } else if (dir == Direction.UP && target >= currentFloor && r.getDirection() == Direction.UP) {
            return target - currentFloor;
        } else if (dir == Direction.DOWN && target <= currentFloor && r.getDirection() == Direction.DOWN) {
            return currentFloor - target;
        } else {
            return Math.abs(currentFloor - target) + 100;
        }
    }
}

class ElevatorController {
    private final List<Elevator> elevators;
    private final DispatchStrategy strategy;

    public ElevatorController(int elevatorCount, DispatchStrategy strategy) {
        this.elevators = new CopyOnWriteArrayList<>();
        for (int i = 1; i <= elevatorCount; i++) {
            elevators.add(new Elevator(i));
        }
        this.strategy = strategy;
    }

    public void requestElevator(int floor, Direction direction) {
        Request req = new Request(floor, direction);
        Elevator selected = strategy.selectElevator(elevators, req);
        System.out.printf("[Controller] Dispatched Elevator %d to floor %d (%s)\n", selected.getId(), floor, direction);
        selected.addRequest(floor);
    }

    public void step() {
        for (Elevator e : elevators) {
            e.step();
        }
    }

    public boolean hasActiveRequests() {
        for (Elevator e : elevators) {
            if (e.hasRequests()) return true;
        }
        return false;
    }

    public List<Elevator> getElevators() { return elevators; }
}

public class Main {
    public static void main(String[] args) {
        ElevatorController controller = new ElevatorController(2, new OptimalDispatchStrategy());
        controller.requestElevator(3, Direction.UP);
        controller.requestElevator(7, Direction.DOWN);

        int ticks = 0;
        while (controller.hasActiveRequests() && ticks < 15) {
            System.out.printf("--- Tick %d ---\n", ++ticks);
            controller.step();
        }
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...