Android System Design Interview Questions
Solve design problems based on a location-based app.
Tier: Less commonDifficulty: Hard
Location questions come as a family, and there are only three shapes in it, nearby search, live tracking, and geofencing. Every one of them lands on the same tensions, accuracy versus battery, update frequency versus data cost, and how much position you're willing to expose to other users or store at all. On Android the permission model is the fourth tension, and it shapes the design more than the other three.
The first thing worth doing out loud is mapping the shape onto the API, because picking wrong here is a rewrite rather than a tweak.
| Feature shape | What the client uses | What the backend does |
|---|---|---|
| Find things near me, once | A single fix from the fused location provider | Geohash or S2 range lookup on a spatial index |
| Live tracking of a moving thing | Fused provider updates from a foreground service typed for location | Writes the latest position, pushes it out on a live channel |
| Notify me on entry or exit | The platform geofencing API | Almost nothing, it only supplies the regions |
What I'd clarify first
- Which of the three shapes is this, since each one is a different Android API and a different backend query, not just a different radius.
- Does it have to keep working when the app isn't on screen, because that's the line between one permission and three, and it decides whether a foreground service exists at all.
- Is approximate location good enough, because a user on Android 12 and up can hand you exactly that whether you asked for it or not.
- Does a fake position pay for anyone here, delivery proof, attendance, a game with rewards, because that turns into a server side problem.
The permission model
This is where most of the actual design problems live, so I'd cover it before anything else.
- Two foreground permissions, not one.
ACCESS_COARSE_LOCATIONgives a position good to roughly a city block.ACCESS_FINE_LOCATIONgives a precise one. Declare coarse always and fine only if the feature truly needs it, because asking for precision you never use is what gets an app questioned in review. - The user can downgrade you. From Android 12 the runtime dialog offers approximate even when you asked for precise, so the feature needs a coarse-only mode that still works, a nearby search with a wider radius rather than a broken screen. You can prompt once to upgrade to precise, and you have to take no for an answer.
- Background is a separate trip.
ACCESS_BACKGROUND_LOCATIONcan't be bundled into the same request. You ask for foreground first, and the background ask sends the user out to Settings to choose allow all the time. Most features don't need it, and Play policy treats it as something you have to justify. - Only this time expires. The one-time grant goes away when the app leaves the foreground, so any code that assumes yesterday's permission is still held is a bug. Check on every entry into the feature, don't cache the answer.
- Foreground service, typed. Anything that keeps taking fixes off screen runs in a foreground service declared
android:foregroundServiceType="location", with the matchingFOREGROUND_SERVICE_LOCATIONpermission and a visible ongoing notification. Without it the process simply stops receiving fixes once the screen goes off, which is the bug that looks like the backend losing updates.
The recurring building blocks
- Fused location provider, not raw GPS, requesting an accuracy and power priority that matches the feature, balanced power for most nearby-search work, high accuracy only for something like turn-by-turn navigation where it genuinely pays.
- A spatial index on the backend, for anything answering what's near this point. Storing raw latitude and longitude and scanning every row for distance doesn't scale. Encoding position into a geohash or an S2 cell turns a proximity search into an indexed range lookup.
- Geofencing through the platform API, rather than polling location and computing distances yourself. The OS does the work and your process doesn't have to be running. The limits are the design problems, though, so say them. Geofences need background location to register at all from Android 10. You get 100 per app, so a large set means registering only the ones near the user and re-registering as they move. They don't survive a reboot, so you listen for boot completed and put them back. And delivery is throttled, a couple of minutes normally and up to six on a device that's been sitting still in Doze, so nothing time critical should hang off one.
- Batched, adaptive updates, tied to detected movement, frequent while actually moving and sparse or stopped while stationary, for any feature that isn't a one-time lookup.
- A plausibility check where location has value. Setting a mock provider takes a developer setting and a free app.
Location.isMockis the cheap client-side check, but the real defence is server side, comparing a claimed position against the last one and rejecting a jump no vehicle could have made.
How I'd reason through a specific variant
For a one-time nearby search, coffee shops near me, it's a single fix at balanced power priority, since a slightly stale position doesn't change the results, sent once to a backend that runs the spatial query.
For continuous tracking, a delivery or a ride, it's the adaptive update loop above running inside a typed foreground service, plus a live channel out, WebSocket or SSE, to deliver position changes to whoever's watching.
For geofencing, alert me near this stop, the platform API does the heavy lifting. The app registers a region and a callback and gets woken by the OS, with no polling at all, as long as it accepts that the wake-up can be minutes late.
Two of those three are worked through in full elsewhere on this site, and they're worth reading as the long versions of this page. Continuous tracking with a server authoritative trip and a driver publishing from a foreground service is the Uber design. Nearby search with a consent and privacy model on top of the geohash is the near by friends design.
Tradeoffs I'd call out
- Update frequency vs battery. This is the tradeoff every variant comes back to. More frequent, higher-accuracy updates make a feature feel live, but continuous high-accuracy GPS is one of the fastest ways to flatten a phone, and users notice and uninstall. Tying frequency to detected movement rather than a fixed timer is almost always the right default.
- Client-computed vs server-computed geospatial logic. A nearby search run client-side against a cached dataset is fast and free, but only works for small, mostly-static data. Anything at real scale, or anything that changes often, needs the index server-side, with the client just sending a position and receiving filtered results.
- Precise vs coarse location. Exact coordinates give the best accuracy, but sharing an exact position with other users, or storing it long-term, is a real privacy exposure. Snapping to a coarser grid, a geohash cell rather than a raw coordinate, is usually accurate enough for the feature at a much smaller footprint. It also means a leak of your database leaks less.
What breaks at scale, offline, and on a poor connection
At scale an unindexed proximity query is what falls over first, so this has to be geospatially indexed on the backend whichever variant is being asked about. Offline there's no honest way to fake a current position, and the right behaviour is showing the last known one with a visible timestamp rather than letting it go quietly stale, while queuing whatever fixes the device does capture to flush on reconnect. On a poor connection, batching those fixes instead of firing a request per fix is what keeps live tracking from either flooding a weak link with tiny frequent requests or falling permanently behind. A queue that grows and flushes in batches degrades gracefully where one request per fix just starts failing.
Read more Request location permissions (opens in a new tab)Create and monitor geofences (opens in a new tab)
Watch