Katkat

Ground Station Registry With Great-Circle Nearest-Online Routing

StripeSoftware Engineer · Technical Screen

Last updated

Build an in-memory registry of satellite ground stations with status tracking, implement the haversine great-circle distance formula, then route an uplink request to the nearest online station. Tests clean API design, careful spherical math, and corner-case handling under time pressure.

You're building the in-memory backend for a satellite operator's uplink scheduler. Each ground station can be online, offline, or in maintenance, and an uplink request should be routed to the closest station that is actually able to take it. The exercise has three parts, each building on the last, meant to be finished in about an hour — it is closer to a math exercise than an algorithms one; most of the risk is in getting the distance formula and the corner cases exactly right.

Part 1 — Registration and status

Implement a GroundStationNetwork class supporting:

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 a duplicate add_station, an unknown station_id, an unknown status string, and an out-of-range coordinate. Status must be a closed set, not a free-form string.

Part 2 — Great-circle distance

Implement haversine_km(lat1, lon1, lat2, lon2) -> float using the haversine formula with Earth radius 6371.0 km. Inputs are decimal degrees.

haversine_km(40.7128, -74.0060, 51.5074, -0.1278) -> 5570.2   (New York to London)
haversine_km(35.0, 139.0, 35.0, 139.0)            -> 0.0     (same point)
haversine_km(0, 0, 0, 180)                        -> 20015.1 (half the circumference)
haversine_km(0, 179.5, 0, -179.5)                 -> 111.2   (crosses the antimeridian)

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. Round distances to 3 decimals. On a tie, the lexicographically smaller station_id wins. Return None (or an empty list) when nothing qualifies.

add_station("svalbard-1",   78.9236,  11.9228)
add_station("awarua-2",    -46.5188, 168.3511)
add_station("fairbanks-3",  64.8378, -147.7164)
set all three "online", then set_status("awarua-2", "maintenance")

nearest_online(61.2181, -149.9003)              -> ("fairbanks-3", 417.228)
k_nearest_online(61.2181, -149.9003, 3)         -> [("fairbanks-3", 417.228), ("svalbard-1", 4385.954)]
nearest_online(61.2181, -149.9003, max_km=100)  -> None
set_status("fairbanks-3", "degraded")           -> ValueError

Constraints

  • Up to 10^4 stations and 10^5 queries in an interview-sized test. A linear scan per query is fine here; be ready to discuss what changes at much larger scale.
  • Latitude must be in [-90, 90] and longitude in [-180, 180].

Hints

Hint 1

Use an enum for status. Parsing a string through the enum constructor rejects unknown values for free.

Hint 2

Trig functions take radians, and clamp the intermediate value into [0, 1] before calling sqrt/asin — floating-point drift can push it just past 1 for nearly antipodal points.

Hint 3

Sort candidates by the tuple (distance, station_id); the tie-break falls out of the sort for free.

Hint 4

Test a pair of points straddling the 180th meridian. Does your formula still report them as close?

Follow-up questions

  • How would you serve nearest-station queries over 5 million ground stations at high query rates?
  • Stations report status over a flaky link. How would you decide a station is offline when it simply stops reporting?
  • Routing an uplink to the geometrically closest station ignores antenna elevation angle and local weather. How would you fold those into the ranking?
  • How would you avoid sending every uplink request in a region to the same single nearest station?