EV Charger Registry with Haversine Nearest-Station Routing
StripeSoftware Engineer · Technical ScreenLast updated
Build an in-memory EV charging station registry with status tracking, a haversine distance function, and nearest-online-station routing with deterministic tie-breaks. Tests clean API design, careful spherical math, and corner-case handling.
You are building the in-memory backend for an EV-charging app. Drivers open the app and want to be pointed at the closest charging station that is actually working. The exercise has three parts, each building on the last. Most of the difficulty is in getting the math and the corner cases right; the algorithms are simple.
Part 1: Station registry and status
Implement a ChargerNetwork class:
add_station(station_id: str, lat: float, lon: float) -> None
set_status(station_id: str, status) -> None # "online" | "offline" | "maintenance"
get_status(station_id: str) -> Status
Decide (and state) what happens for duplicate registrations, unknown ids, unknown status values, and out-of-range coordinates. Statuses must be a closed set, not free-form strings.
Part 2: Great-circle distance
Implement haversine_km(lat1, lon1, lat2, lon2) -> float using the haversine formula with an Earth radius of 6371.0 km. Inputs are decimal degrees.
haversine_km(48.8566, 2.3522, 52.5200, 13.4050) -> 877.5 (rounded to 0.1)
haversine_km(10, 20, 10, 20) -> 0.0
haversine_km(0, 0, 0, 180) -> 20015.1 (half the circumference)
haversine_km(0, 179.5, 0, -179.5) -> 111.2 (crosses the antimeridian)
Part 3: Route a driver
Add:
nearest_online(lat, lon, max_km=None) -> tuple[str, float] | None
k_nearest_online(lat, lon, k, max_km=None) -> list[tuple[str, float]]
Only online stations are eligible. Return distances rounded to 3 decimals. If two stations are equally far, the lexicographically smaller id wins. Return None (or an empty list) when nothing qualifies.
add_station("lyon-1", 45.7640, 4.8357)
add_station("geneva-2", 46.2044, 6.1432)
add_station("turin-3", 45.0703, 7.6869)
set all three "online", then set_status("geneva-2", "maintenance")
nearest_online(45.8992, 6.1294) -> ("lyon-1", 101.352)
k_nearest_online(45.8992, 6.1294, 3) -> [("lyon-1", 101.352), ("turin-3", 152.436)]
nearest_online(45.8992, 6.1294, max_km=50) -> None
set_status("lyon-1", "broken") -> ValueError
Constraints
- Up to 10^4 stations and 10^5 queries in an interview-sized test. A linear scan per query is acceptable here; be ready to discuss what changes at a much larger scale.
- Latitude must be in [-90, 90] and longitude in [-180, 180].
Hints
Hint 1
Use an Enum for statuses. Parsing a string through it rejects unknown values for free.
Hint 2
Trig functions take radians. Also think about what happens to sqrt(a) and asin when float error pushes a just above 1.
Hint 3
Sorting candidates by the tuple (distance, id) handles the tie-break without extra code.
Hint 4
Test a pair of points on opposite sides of the 180th meridian. Does your distance come out small?
Follow-up questions
- How would you serve nearest-station queries over 5 million stations at 20k QPS?
- Status reports arrive from flaky devices. How would you mark a station offline when it stops reporting?
- Drivers care about driving distance, not straight-line distance. How would you change the ranking without calling a routing API for every station?
- How would you spread load so that every driver in a city is not sent to the same station?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.