Machine Coding Problem

ETA Calculator

macoAllgeodijkstra-with-live-weights
Commonly Asked By:UberLyftGoogle

Requirements & System Scope

Functional Scope (In-Scope)

  • Road Graph Dijkstra Solver: Calculates routes dynamically using base segment travel times and active mode speed modifiers.
  • Dynamic Traffic Layers: Modifies edge multipliers on-the-fly to model accidents, dynamic speed boundaries, and gridlock constraints.
  • Transport Mode Enforcement: Enforces node restrictions (e.g. preventing walking routes from taking highways, or cars from taking footpaths).
  • Automatic Route Re-Routing: Tracks user coordinates, automatically calculating a fresh route from their current location if they drift > 50m for more than 10 seconds.

Explicit Boundaries (Out-of-Scope)

  • Multi-Destination Traveling Salesperson Solving: Focuses strictly on point-to-point Dijkstra paths.
  • Elevation Slope Adjustments: Excludes detailed grade vectors from time formulas to prioritize traffic and execution speed.

Class Diagram & Entity Relationships

Adjacency lists, Dijkstra priorities, and deviation monitoring loops:

Loading...
  • RoadNode & RoadEdge: Build the core spatial adjacency maps.
  • ETAEngine: Computes routes using Dijkstra's shortest-path algorithm.
  • RouteMonitor: Tracks coordinates, checks limits, and triggers path updates.

Design Patterns & SOLID Principles

  • Strategy Pattern: Encapsulates travel mode formulas (Car, Bike, Walking) as independent, interchangeable speed adapters.
  • State Machine Pattern: Tracks user deviation states (On-Route, Deviated-Warning, Rerouting).
  • Single Responsibility Principle (SRP): Isolates graph generation, Dijkstra search algorithms, speed calculations, and deviation tracking.

Core Execution Workflows

Dijkstra Routing & Deviation Detections

  1. Dijkstra Route Solving:
    1. Initialize distances to all nodes as Infinity, and the start node as 0.
    2. Add the start node to the priority queue.
    3. For each node, iterate over outgoing edges. Skip edges that block the selected transport mode.
    4. Calculate edge weights using traffic multipliers: weight = baseTime * liveMultiplier * modeFactor. Relax edges and update predecessors.
  2. Deviation Trigger Checks:
    1. Calculate the minimum distance between the user's current GPS coordinates and the planned route nodes using the Haversine formula.
    2. If the user is within 50 meters of the path, reset the timer.
    3. If they are further than 50 meters away, start the deviation timer. If they remain off-path for more than 10 seconds, trigger re-routing.

Concurrency & Thread Safety Strategy

Ensuring routes remain accurate during concurrent updates:

  • Atomic Traffic Multipliers: Edges use volatile variables to ensure real-time traffic updates are immediately visible to active routing threads.
  • Concurrent Graph Collections: Utilizes thread-safe copy-on-write lists and concurrent maps to prevent race conditions when updating the road network.

Complete Clean Code Blueprint

Production reference implementations demonstrating Dijkstra pathfinders, live traffic multipliers, and GPS route monitors in Java and Python:

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

enum TransportMode {
    CAR(1.0),
    BIKE(2.0),      // 2x slower than car base speed
    WALKING(6.0);   // 6x slower than car base speed

    private final double speedFactor;
    TransportMode(double speedFactor) {
        this.speedFactor = speedFactor;
    }
    public double getSpeedFactor() { return speedFactor; }
}

class GeoPoint {
    private final double lat;
    private final double lng;

    public GeoPoint(double lat, double lng) {
        this.lat = lat;
        this.lng = lng;
    }

    public double getLat() { return lat; }
    public double getLng() { return lng; }

    public double distanceTo(GeoPoint other) {
        double R = 6371000.0; // Earth's radius in meters
        double latDist = Math.toRadians(other.lat - this.lat);
        double lngDist = Math.toRadians(other.lng - this.lng);
        double a = Math.sin(latDist / 2) * Math.sin(latDist / 2)
                + Math.cos(Math.toRadians(this.lat)) * Math.cos(Math.toRadians(other.lat))
                * Math.sin(lngDist / 2) * Math.sin(lngDist / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return R * c;
    }
}

class RoadNode {
    private final String nodeId;
    private final GeoPoint location;

    public RoadNode(String nodeId, GeoPoint location) {
        this.nodeId = nodeId;
        this.location = location;
    }

    public String getNodeId() { return nodeId; }
    public GeoPoint getLocation() { return location; }
}

class RoadEdge {
    private final String edgeId;
    private final String sourceId;
    private final String targetId;
    private final double baseTravelTimeSeconds;
    private final Set<TransportMode> allowedModes;
    private volatile double liveMultiplier = 1.0;

    public RoadEdge(String edgeId, String sourceId, String targetId, double baseTravelTimeSeconds, Set<TransportMode> allowedModes) {
        this.edgeId = edgeId;
        this.sourceId = sourceId;
        this.targetId = targetId;
        this.baseTravelTimeSeconds = baseTravelTimeSeconds;
        this.allowedModes = new HashSet<>(allowedModes);
    }

    public double getWeight(TransportMode mode) {
        if (!allowedModes.contains(mode)) {
            return Double.MAX_VALUE;
        }
        return baseTravelTimeSeconds * liveMultiplier * mode.getSpeedFactor();
    }

    public void setLiveMultiplier(double multiplier) {
        this.liveMultiplier = multiplier;
    }

    public String getEdgeId() { return edgeId; }
    public String getSourceId() { return sourceId; }
    public String getTargetId() { return targetId; }
    public double getBaseTravelTimeSeconds() { return baseTravelTimeSeconds; }
    public double getLiveMultiplier() { return liveMultiplier; }
    public Set<TransportMode> getAllowedModes() { return allowedModes; }
}

class RoadGraph {
    private final ConcurrentHashMap<String, RoadNode> nodes = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, List<RoadEdge>> adjacencyList = new ConcurrentHashMap<>();

    public void addNode(RoadNode node) {
        nodes.put(node.getNodeId(), node);
    }

    public void addEdge(RoadEdge edge) {
        adjacencyList.computeIfAbsent(edge.getSourceId(), k -> new CopyOnWriteArrayList<>()).add(edge);
    }

    public RoadNode getNode(String nodeId) {
        return nodes.get(nodeId);
    }

    public List<RoadEdge> getOutgoingEdges(String nodeId) {
        return adjacencyList.getOrDefault(nodeId, Collections.emptyList());
    }

    public ConcurrentHashMap<String, RoadNode> getNodes() { return nodes; }
}

class Route {
    private final List<String> nodeIds;
    private final double totalTimeSeconds;

    public Route(List<String> nodeIds, double totalTimeSeconds) {
        this.nodeIds = new ArrayList<>(nodeIds);
        this.totalTimeSeconds = totalTimeSeconds;
    }

    public List<String> getNodeIds() { return nodeIds; }
    public double getTotalTimeSeconds() { return totalTimeSeconds; }
}

class ETAEngine {
    private final RoadGraph graph;

    public ETAEngine(RoadGraph graph) {
        this.graph = graph;
    }

    // Dijkstra's Routing Algorithm with Dynamic Weights and Mode Filters
    public Route calculateFastestRoute(String sourceId, String targetId, TransportMode mode) {
        if (graph.getNode(sourceId) == null || graph.getNode(targetId) == null) {
            return null;
        }

        PriorityQueue<NodeState> pq = new PriorityQueue<>(Comparator.comparingDouble(n -> n.timeAccumulated));
        Map<String, Double> minTimes = new HashMap<>();
        Map<String, String> predecessors = new HashMap<>();

        minTimes.put(sourceId, 0.0);
        pq.add(new NodeState(sourceId, 0.0));

        while (!pq.isEmpty()) {
            NodeState current = pq.poll();

            if (current.timeAccumulated > minTimes.getOrDefault(current.nodeId, Double.MAX_VALUE)) {
                continue;
            }

            if (current.nodeId.equals(targetId)) {
                break; // Shortest path discovered
            }

            for (RoadEdge edge : graph.getOutgoingEdges(current.nodeId)) {
                double weight = edge.getWeight(mode);
                if (weight == Double.MAX_VALUE) continue; // Route mode blocked

                double newTime = current.timeAccumulated + weight;
                if (newTime < minTimes.getOrDefault(edge.getTargetId(), Double.MAX_VALUE)) {
                    minTimes.put(edge.getTargetId(), newTime);
                    predecessors.put(edge.getTargetId(), current.nodeId);
                    pq.add(new NodeState(edge.getTargetId(), newTime));
                }
            }
        }

        if (!minTimes.containsKey(targetId)) {
            return null; // Route unreachable
        }

        // Reconstruct shortest time path
        List<String> path = new LinkedList<>();
        String curr = targetId;
        while (curr != null) {
            path.add(0, curr);
            curr = predecessors.get(curr);
        }

        return new Route(path, minTimes.get(targetId));
    }

    private static class NodeState {
        private final String nodeId;
        private final double timeAccumulated;

        public NodeState(String nodeId, double timeAccumulated) {
            this.nodeId = nodeId;
            this.timeAccumulated = timeAccumulated;
        }
    }
}

class RouteMonitor {
    private final RoadGraph graph;
    private final ETAEngine etaEngine;
    private final String userId;
    private Route activeRoute;
    private TransportMode activeMode;
    private String targetNodeId;
    
    private volatile long deviationStartMs = 0L;
    private final double maxAllowedDeviationMeters = 50.0;
    private final long deviationThresholdMs = 10000; // 10 seconds

    public RouteMonitor(String userId, RoadGraph graph, ETAEngine etaEngine) {
        this.userId = userId;
        this.graph = graph;
        this.etaEngine = etaEngine;
    }

    public void startRoute(Route route, TransportMode mode, String destinationNodeId) {
        this.activeRoute = route;
        this.activeMode = mode;
        this.targetNodeId = destinationNodeId;
        this.deviationStartMs = 0L;
    }

    // Dynamic Deviation and Re-routing Pipeline
    public Route processGPSUpdate(GeoPoint currentGPS) {
        if (activeRoute == null) return null;

        // Calculate minimum distance from GPS coordinate to nodes on planned route
        double minDistance = Double.MAX_VALUE;
        String nearestNode = null;

        for (String nodeId : activeRoute.getNodeIds()) {
            RoadNode node = graph.getNode(nodeId);
            if (node != null) {
                double distance = currentGPS.distanceTo(node.getLocation());
                if (distance < minDistance) {
                    minDistance = distance;
                    nearestNode = nodeId;
                }
            }
        }

        // Check if user has drifted past threshold bounds
        if (minDistance > maxAllowedDeviationMeters) {
            long now = System.currentTimeMillis();
            if (deviationStartMs == 0L) {
                deviationStartMs = now;
            } else if (now - deviationStartMs >= deviationThresholdMs) {
                System.out.println("ROUTE MONITOR -> User " + userId + " deviated by " + minDistance + "m. RE-ROUTING...");
                
                // Trigger re-routing using nearest active node as new origin
                Route newRoute = etaEngine.calculateFastestRoute(nearestNode, targetNodeId, activeMode);
                if (newRoute != null) {
                    startRoute(newRoute, activeMode, targetNodeId);
                    return newRoute;
                }
            }
        } else {
            deviationStartMs = 0L; // Reset timer if returned to safe path
        }
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("=== JAVA ETA CALCULATOR SIMULATION ===");
        RoadGraph graph = new RoadGraph();
        
        RoadNode n1 = new RoadNode("A", new GeoPoint(40.7128, -74.0060));
        RoadNode n2 = new RoadNode("B", new GeoPoint(40.7130, -74.0070));
        RoadNode n3 = new RoadNode("C", new GeoPoint(40.7140, -74.0080));
        graph.addNode(n1);
        graph.addNode(n2);
        graph.addNode(n3);
        
        Set<TransportMode> allModes = new HashSet<>(Arrays.asList(TransportMode.values()));
        RoadEdge e1 = new RoadEdge("e-1", "A", "B", 60.0, allModes);
        RoadEdge e2 = new RoadEdge("e-2", "B", "C", 120.0, allModes);
        graph.addEdge(e1);
        graph.addEdge(e2);
        
        ETAEngine engine = new ETAEngine(graph);
        Route route = engine.calculateFastestRoute("A", "C", TransportMode.CAR);
        
        if (route != null) {
            System.out.println("Route found: " + route.getNodeIds() + " | Time: " + route.getTotalTimeSeconds() + "s");
        } else {
            System.out.println("No route found.");
        }
        
        System.out.println("=== END OF JAVA SIMULATION ===");
    }
}

๐Ÿ’ฌ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...