Android System Design Interview Questions
How does "Where Is My Train" work without internet?
Tier: Less commonDifficulty: Medium
Explain how an app could estimate a train's progress when mobile data is unavailable. The user should still be able to see approximately where they are along a known route using information already on the phone and available device signals.
The problem
Assume the user selects the train and the app has downloaded route and timetable data beforehand. Decide which local signals can help estimate progress, how to represent uncertainty and how much storage and battery the feature may use.
For example, Ana downloads a route before boarding and later loses mobile data. If the phone still observes recognizable cell towers, the app can compare them with an offline route map to estimate the current segment. If there is no useful signal, it should show uncertainty or the last known position rather than invent a precise location.
No internet and no radio signal are different conditions. The walkthrough explores a possible design for the interview, not verified internal code of the named app.
Starting the design
An app can estimate train progress without mobile data by matching nearby cell towers to route data already on the phone. The product publicly describes offline tracking. The design below is one way to build it for an interview, not a verified description of its internal code.
What I'd clarify first
- Does the user tell us which train they are on, because knowing the route turns a coarse position into a useful one.
- How large can the offline bundle be, since the tower map and the timetable both have to fit on a cheap phone.
- Is battery a constraint, given a journey is hours long and the screen is often on.
The mechanism
- An offline bundle, a map of tower ids to positions along the network, plus the timetable for every route.
- A reading of the currently camped tower, taken from the telephony APIs, which need no data connection.
- A match of the observed tower sequence against the chosen route, which is what turns a coarse reading into a position.
- A delay estimate, the difference between where the train is and where the schedule says it should be.
One way to build the tower map is to collect paired cell and location readings with user consent, check them and combine them into downloadable route data. A new route would start with little evidence. More journeys would improve coverage, although that depends on how many useful readings users contribute.
The timetable is the other half, and it is easy to forget. Position alone cannot tell you a train is late. The delay is position against schedule, so both have to be on the device.
I'd match a sequence of towers instead of trusting each reading on its own. A single tower can cover kilometres. Several towers seen in the route's order give a better estimate and can reject an unexpected reading. The matcher needs to know the likely direction, with a way to recover if the train reverses or the user chose the wrong train. It can then estimate which side of a station the train has reached.
The permission, which is the real constraint
No data connection is needed, but location permission is. For apps targeting Android 10 and up, getAllCellInfo() and requestCellInfoUpdate() require ACCESS_FINE_LOCATION at runtime, where coarse used to be enough. That permission is foreground only unless you also ask for background location, so the readings come while the app is on screen. For this app that is fine, the user is looking at the screen because they want to know where the train is.
// API 29 and up, and it needs ACCESS_FINE_LOCATION granted at runtime.
telephony.requestCellInfoUpdate(executor, object : TelephonyManager.CellInfoCallback() {
override fun onCellInfo(cells: MutableList<CellInfo>) {
// isRegistered marks the tower the phone is actually camped on.
val camped = cells.firstOrNull { it.isRegistered }
}
})
Why this works and what it trades away
The accuracy is coarse. A tower can cover a few hundred metres in a city and several kilometres in open country, so the answer is "somewhere near this tower", not a GPS coordinate. That is an acceptable trade, because a passenger wants to know roughly where the train is and how late it is, not a coordinate.
Battery is the cost nobody mentions. Asking for a fresh cell reading every few seconds for a six hour journey adds up. The cadence is tied to how fast the train is moving, and it drops right down when the tower has not changed.
Once the device does have internet, the server takes over, because live position and delay from the operator beat anything derived on the phone. The precedence rule is worth stating. A fresh server position wins, and the tower estimate fills the gaps in between rather than being averaged with it.
What I'd call out as the interesting design choice
This works precisely because it does not try to be precise. It accepts tower level accuracy in exchange for a signal that is already there for free, with no GPS lock to wait for. A design built on offline GPS instead would still need a view of the sky, which a train interior does not reliably give you. That is the constraint the tower approach sidesteps.
How I'd build this on Android
I'd present matching cell towers to routes as a possible interview design. The public product site confirms offline train tracking, but does not confirm the exact database, update protocol or crowdsourcing algorithm described here. Those parts are design choices for this answer. See Where is my Train.
interface TrainPositionRepository {
fun estimate(trainId: String): Flow<TrainEstimate>
}
data class TrainEstimate(
val stationName: String?, val confidence: String, val observedAtMillis: Long,
)
class TrainViewModel(saved: SavedStateHandle, repository: TrainPositionRepository) : ViewModel() {
val estimate = repository.estimate(checkNotNull(saved["trainId"]))
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
}
The repository combines cell readings, an indexed route database and a matching algorithm. Room or another local store holds versioned route data. A worker downloads and checks a new bundle before switching to it. If validation fails, the app keeps the previous version. A query must never see half the old timetable and half the new one.
The route collects estimates with lifecycle awareness and shows their confidence and age. It shows an unknown position if there is no usable cell reading. That can happen with disabled location services, denied precise permission, no cellular radio or old readings. Turning off mobile data is different from airplane mode, which may remove the cell readings too.
I'd inject the tower source, route DAO and clock. Compose does not call TelephonyManager directly.
I'd check these cases.
- An unknown tower.
- A route fork.
- Stale readings.
- A bundle update during a journey.
Trains can reverse direction, so an algorithm that only moves forward needs either an agreed assumption or a way to recover. A position estimated from the timetable must not be shown as a measured live location.
Read more Privacy changes in Android 10 (opens in a new tab)