August 31, 2026
Designing a Location-Based Activity Tracking System β Android System Design Interview
One of the most interesting Android system-design problems is designing an application that continuously tracks a userβs physical activity.

By Ninad Bhase
21 min read
At first glance, the problem looks straightforward:
GPS β Calculate Distance β Show RouteGPS β Calculate Distance β Show RouteBut an Android interview is rarely testing whether you know how to call FusedLocationProviderClient.
The interviewer is testing whether you can reason about:
- Android lifecycle
- Background execution
- Foreground Services
- Location accuracy
- Battery consumption
- Local persistence
- Process death
- Offline support
- Synchronization
- UI architecture
- Concurrency
- Failure recovery
- Trade-offs
More importantly, they want to understand why you made each architectural decision.
This article approaches the problem exactly as I would approach it in an SDE-2 Android system-design interview.
The goal isn't to design an entire fitness product.
The goal is to demonstrate:
How an Android engineer thinks when given an ambiguous system-design problem.
1. The Interview Question
Imagine the interviewer says:
"Design an Android application that allows users to record outdoor activities using their location."
That's it.
There is no architecture.
No database.
No API.
No mention of Compose.
No mention of Services.
This is where many candidates immediately start saying:
"I'll use MVVM, Clean Architecture, Room, Retrofit and a Foreground Service."
That's premature.
The first thing I should do is clarify the problem.
2. Step One β Ask Clarifying Questions
Before designing anything, I would ask:
Question 1
"Should activity tracking continue when the application goes into the background or the screen is locked?"
This is probably the most important question.
If the answer is yes, our architecture immediately changes.
We cannot depend on:
Activity
Fragment
Composable
ViewModelActivity
Fragment
Composable
ViewModelto own continuous location tracking.
We will need a component capable of performing long-running, user-visible work independently of the UI lifecycle.
That leads us toward a Foreground Service.
Question 2
"Should the activity work when there is no internet connection?"
If yes, recording cannot depend on the backend.
We need:
GPS
β
Local StorageGPS
β
Local Storagerather than:
GPS
β
APIGPS
β
APIThis leads us toward an offline-first recording architecture.
Question 3
"What happens to the activity if the application process is recreated?"
This tells us whether activity state needs to be durable.
If the answer is:
"The user shouldn't lose their activity."
then critical recording state cannot exist only in memory.
We need persistent storage.
Question 4
"Do we need multiple activity types such as running, walking and cycling?"
If yes, we should avoid creating completely separate tracking engines.
We should design:
Tracking Engine
β
ββββββββββββββΌβββββββββββββ
βΌ βΌ βΌ
Running Walking CyclingTracking Engine
β
ββββββββββββββΌβββββββββββββ
βΌ βΌ βΌ
Running Walking CyclingThe common location infrastructure should be reusable.
Question 5
"Do we need to upload the activity immediately after completion?"
If immediate upload isn't mandatory, we can decouple recording from synchronization.
That gives us:
Recording
β
Local DB
β
Background SyncRecording
β
Local DB
β
Background SyncThis is much more resilient.
3. Step Two β Define the Scope
At this point I would explicitly tell the interviewer:
"I'll focus on the core activity-recording experience first. I'll keep social features and advanced analytics out of scope unless we have additional time."
This is important.
A common system-design mistake is trying to design everything.
For this interview, I'll keep the scope to:
In Scope
- Start activity
- Pause activity
- Resume activity
- Finish activity
- Location tracking
- Distance
- Speed
- Pace
- Route
- Background tracking
- Local persistence
- Offline support
- Activity synchronization
- Activity history
Out of Scope
- Social feed
- Messaging
- Challenges
- Recommendations
- Ads
- Advanced machine learning
- Complex social graphs
This gives us a manageable critical user journey.
4. Identify the Critical User Journey
The most important flow is:
Start Activity
β
Record Location
β
Calculate Metrics
β
Persist Data
β
Continue in Background
β
Finish Activity
β
SynchronizeStart Activity
β
Record Location
β
Calculate Metrics
β
Persist Data
β
Continue in Background
β
Finish Activity
β
SynchronizeIf this flow fails, the core product fails.
That means this flow deserves the highest engineering attention.
This is an important system-design principle:
Not every feature deserves equal architectural complexity. Focus on the critical user journey first.
5. Step Three β Define Functional Requirements
The core application should allow the user to:
Start Activity
Pause Activity
Resume Activity
Finish ActivityStart Activity
Pause Activity
Resume Activity
Finish ActivityDuring recording:
Distance
Duration
Speed
Pace
RouteDistance
Duration
Speed
Pace
RouteAfter completion:
Activity Summary
Activity History
Activity DetailsActivity Summary
Activity History
Activity DetailsAnd the system should support:
Background Recording
Offline Recording
SynchronizationBackground Recording
Offline Recording
Synchronization6. Step Four β Define Non-Functional Requirements
This is where the interesting engineering begins.
Reliability
The activity should not be lost because of:
- Network failure
- Screen lock
- Backgrounding
- Process recreation
- Temporary GPS issues
- Upload failure
Battery Efficiency
Continuous GPS is expensive.
We need to balance:
Accuracy
β
BatteryAccuracy
β
BatteryPerformance
GPS updates may arrive frequently.
We don't want:
GPS update
β
Entire Compose hierarchy recomposes
β
Expensive map renderingGPS update
β
Entire Compose hierarchy recomposes
β
Expensive map renderingOffline Support
Recording should work without network connectivity.
Scalability
We should avoid loading thousands of GPS points into memory unnecessarily.
7. Now Draw the First Architecture
Only now would I draw the architecture.
Start simple:
Compose UI
β
βΌ
ViewModel
β
βΌ
Activity Tracker
β
βΌ
Foreground Service
β
βΌ
Location ProviderCompose UI
β
βΌ
ViewModel
β
βΌ
Activity Tracker
β
βΌ
Foreground Service
β
βΌ
Location ProviderThen explain:
"The key architectural requirement is that location tracking must be independent of the UI lifecycle."
This is the first major design decision.
8. Why Can't Compose Own Location Tracking?
Suppose we do:
Composable
β
Location ListenerComposable
β
Location ListenerThe problem is that a Composable is part of the UI lifecycle.
The UI can:
Recompose
Leave composition
Be recreated
Be destroyedRecompose
Leave composition
Be recreated
Be destroyedBut the activity is still running.
For example:
User starts activity
β
Screen is visible
β
User locks phone
β
UI disappears
β
Activity should continueUser starts activity
β
Screen is visible
β
User locks phone
β
UI disappears
β
Activity should continueTherefore:
The UI should control the activity, but it should not own the activity-recording engine.
9. Why Not Put Tracking in the ViewModel?
This is a better question.
A ViewModel survives configuration changes, so someone might argue:
"Why not just put GPS tracking inside the ViewModel?"
Because the ViewModel is still fundamentally tied to the UI/navigation lifecycle.
We want:
UI lifecycle
β
Recording lifecycleUI lifecycle
β
Recording lifecycleThe recording may continue even when the recording screen is no longer present.
Therefore:
Compose
β
ViewModel
β
Command
β
Tracking LayerCompose
β
ViewModel
β
Command
β
Tracking LayerThe ViewModel observes the tracking state instead of owning the actual long-running operation.
10. The Foreground Service
Now the interviewer will probably ask:
"What Android component would you use for continuous location tracking?"
My answer:
"For an actively recording, user-visible activity, I would use a Foreground Service. The service owns the long-running tracking operation, while the UI observes the resulting state."
Architecture:
UI
β
βΌ
ViewModel
β
βΌ
Use Case
β
βΌ
Activity Tracker
β
βΌ
Foreground Service
β
βΌ
FusedLocationProviderClientUI
β
βΌ
ViewModel
β
βΌ
Use Case
β
βΌ
Activity Tracker
β
βΌ
Foreground Service
β
βΌ
FusedLocationProviderClientThe Foreground Service also exposes an ongoing notification so the user knows tracking is active.
11. Foreground Service vs WorkManager
This is a very likely interview follow-up.
Interviewer:
"Why not use WorkManager?"
Answer:
"WorkManager is designed for deferrable, persistent background work. Continuous activity recording is an active user-visible operation, so I would use a Foreground Service for recording. I would use WorkManager later for deferred synchronization."
Therefore:
ACTIVE RECORDING
β
Foreground Service
DEFERRED UPLOAD
β
WorkManagerACTIVE RECORDING
β
Foreground Service
DEFERRED UPLOAD
β
WorkManagerThis distinction is important.
Don't use one Android component for every background problem.
12. Activity State Machine
Now we need to define activity states.
START
β
βΌ
RECORDING
/ \
/ \
PAUSE FINISH
β β
βΌ βΌ
PAUSED COMPLETED
β
RESUME
β
βΌ
RECORDINGSTART
β
βΌ
RECORDING
/ \
/ \
PAUSE FINISH
β β
βΌ βΌ
PAUSED COMPLETED
β
RESUME
β
βΌ
RECORDINGIn Kotlin:
sealed interface ActivityState {
data object Idle : ActivityState
data object Recording : ActivityState
data object Paused : ActivityState
data object Completed : ActivityState
}sealed interface ActivityState {
data object Idle : ActivityState
data object Recording : ActivityState
data object Paused : ActivityState
data object Completed : ActivityState
}This gives us explicit valid transitions.
For example:
Idle β Recording
Recording β Paused
Paused β Recording
Recording β Completed
Paused β CompletedIdle β Recording
Recording β Paused
Paused β Recording
Recording β Completed
Paused β CompletedBut:
Idle β CompletedIdle β Completedshouldn't happen.
13. Why a State Machine?
The interviewer may ask:
"Why not just maintain three booleans?"
For example:
isRecording
isPaused
isCompletedisRecording
isPaused
isCompletedThis allows invalid combinations:
isRecording = true
isPaused = true
isCompleted = trueisRecording = true
isPaused = true
isCompleted = trueA state machine makes the valid states explicit.
It also makes behavior easier to reason about and test.
This is a good SDE-2 principle:
When a domain has mutually exclusive states and transitions, model the states explicitly.
14. Location Tracking
Now we reach the actual location layer.
Android provides:
FusedLocationProviderClientFusedLocationProviderClientConceptually:
GPS / Network Providers
β
FusedLocationProviderClient
β
LocationCallback
β
Location ProcessorGPS / Network Providers
β
FusedLocationProviderClient
β
LocationCallback
β
Location ProcessorA location point might contain:
data class LocationPoint(
val latitude: Double,
val longitude: Double,
val altitude: Double?,
val accuracy: Float,
val speed: Float?,
val timestamp: Long
)data class LocationPoint(
val latitude: Double,
val longitude: Double,
val altitude: Double?,
val accuracy: Float,
val speed: Float?,
val timestamp: Long
)15. Don't Trust Every GPS Point
This is where a basic implementation becomes a production implementation.
GPS isn't perfect.
Imagine:
Point A
β
Point B
β
Point CPoint A
β
Point B
β
Point CThe user is actually standing still.
GPS might still report:
A β B = 8m
B β C = 12mA β B = 8m
B β C = 12mIf we blindly add those distances:
Actual movement = 0m
Calculated movement = 20mActual movement = 0m
Calculated movement = 20mThis creates inaccurate activity data.
Therefore, introduce a:
LocationFilterLocationFilter16. Location Filtering
The processor can consider:
- Accuracy
- Timestamp
- Minimum displacement
- Unrealistic speed
- Duplicate points
For example:
if (location.accuracy > MAX_ACCURACY) {
return
}if (location.accuracy > MAX_ACCURACY) {
return
}We can also reject impossible movement.
If a user is running and suddenly reports:
Speed = 180 km/hSpeed = 180 km/hwe should question the point rather than blindly accepting it.
The exact thresholds depend on the product and activity type.
17. Calculate Distance Incrementally
Suppose we have:
10,000 GPS points10,000 GPS pointsA naive implementation might repeatedly calculate:
Point 1 β Point 2
Point 2 β Point 3
...Point 1 β Point 2
Point 2 β Point 3
...over the entire list whenever a new point arrives.
That is unnecessary.
Instead:
Previous Point
+
Current Point
β
Distance Between Them
β
Add To TotalPrevious Point
+
Current Point
β
Distance Between Them
β
Add To TotalSo:
A β B = 10m
B β C = 15m
C β D = 12m
Total = 37mA β B = 10m
B β C = 15m
C β D = 12m
Total = 37mEach new location can be processed incrementally.
18. Metrics Calculation
We can have a dedicated:
MetricsCalculatorMetricsCalculatorIt can calculate:
Distance
Total Distance
=
Ξ£ distance(previousPoint, currentPoint)Total Distance
=
Ξ£ distance(previousPoint, currentPoint)Duration
Duration
=
Current Time - Start TimeDuration
=
Current Time - Start TimeAverage Speed
Average Speed
=
Distance / TimeAverage Speed
=
Distance / TimePace
For running/walking:
Pace
=
Elapsed Time / DistancePace
=
Elapsed Time / DistanceFor example:
5 km
30 minutes
Pace = 6 min/km5 km
30 minutes
Pace = 6 min/km19. Separate Location Processing from Metrics
Don't put everything inside the Service.
Avoid:
ActivityTrackingService
βββ GPS
βββ Distance
βββ Speed
βββ Pace
βββ Database
βββ Network
βββ UIActivityTrackingService
βββ GPS
βββ Distance
βββ Speed
βββ Pace
βββ Database
βββ Network
βββ UIInstead:
Foreground Service
β
LocationTracker
β
LocationProcessor
β
βββββββΌββββββββββββ
βΌ βΌ βΌ
Filter Distance Metrics
β
βΌ
RepositoryForeground Service
β
LocationTracker
β
LocationProcessor
β
βββββββΌββββββββββββ
βΌ βΌ βΌ
Filter Distance Metrics
β
βΌ
RepositoryThis makes the system easier to test.
For example:
LocationProcessorLocationProcessorcan be tested without starting an Android Service.
20. Where Should the Data Be Stored?
Now the interviewer asks:
"Where would you store the location data?"
My answer:
"Since activity recording must survive network failure and potentially process recreation, I would persist the active activity locally using Room."
The flow becomes:
Location
β
Process
β
Room
β
UILocation
β
Process
β
Room
β
UIRather than:
Location
β
UILocation
β
UI21. Room Data Model
We can have two primary entities.
Activity
@Entity(tableName = "activities")
data class ActivityEntity(
@PrimaryKey
val activityId: String,
val type: String,
val startTime: Long,
val endTime: Long?,
val durationSeconds: Long,
val distanceMeters: Double,
val uploadStatus: String
)@Entity(tableName = "activities")
data class ActivityEntity(
@PrimaryKey
val activityId: String,
val type: String,
val startTime: Long,
val endTime: Long?,
val durationSeconds: Long,
val distanceMeters: Double,
val uploadStatus: String
)Location Point
@Entity(tableName = "activity_points")
data class ActivityPointEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val activityId: String,
val latitude: Double,
val longitude: Double,
val altitude: Double?,
val timestamp: Long
)@Entity(tableName = "activity_points")
data class ActivityPointEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val activityId: String,
val latitude: Double,
val longitude: Double,
val altitude: Double?,
val timestamp: Long
)Relationship:
Activity
β
βββ Point 1
βββ Point 2
βββ Point 3
βββ ...
βββ Point NActivity
β
βββ Point 1
βββ Point 2
βββ Point 3
βββ ...
βββ Point N22. Why Separate Activity and Location Points?
The interviewer may ask:
"Why not store the entire route inside the Activity object?"
Because a route can contain thousands of points.
Separating them gives us better control over:
- Inserts
- Queries
- Memory
- Pagination
- Route processing
It also avoids constantly rewriting a huge activity object.
23. Room as the Source of Truth
This is an important architectural decision.
We could have:
GPS β Memory β UIGPS β Memory β UIBut memory is not durable.
Instead:
GPS
β
Room
β
Flow
β
ViewModel
β
ComposeGPS
β
Room
β
Flow
β
ViewModel
β
ComposeNow the database becomes the durable source of truth.
If the UI disappears:
Room
β
still existsRoom
β
still existsIf the network disappears:
Room
β
still worksRoom
β
still worksIf the process is recreated:
Room
β
recover activityRoom
β
recover activity24. Offline-First Design
Now the interviewer asks:
"What if the user loses internet connectivity while recording?"
This should not affect recording.
The architecture should be:
GPS
β
βΌ
Local Room
β
uploadStatus
β
βββββββββββ΄ββββββββββ
βΌ βΌ
Connected Offline
β β
βΌ βΌ
Upload Keep Local
β
βΌ
Sync LaterGPS
β
βΌ
Local Room
β
uploadStatus
β
βββββββββββ΄ββββββββββ
βΌ βΌ
Connected Offline
β β
βΌ βΌ
Upload Keep Local
β
βΌ
Sync LaterThis is an offline-first recording architecture.
The user should be able to complete the activity even without internet.
25. Upload Status
We can maintain:
PENDING
UPLOADING
SUCCESS
FAILEDPENDING
UPLOADING
SUCCESS
FAILEDFor example:
enum class UploadStatus {
PENDING,
UPLOADING,
SUCCESS,
FAILED
}enum class UploadStatus {
PENDING,
UPLOADING,
SUCCESS,
FAILED
}When the user finishes:
Activity Completed
β
uploadStatus = PENDINGActivity Completed
β
uploadStatus = PENDINGThen synchronization can happen independently.
26. WorkManager for Synchronization
Now:
Activity Completed
β
Room
β
PENDING
β
WorkManager
β
Network Available
β
Upload
β
SUCCESSActivity Completed
β
Room
β
PENDING
β
WorkManager
β
Network Available
β
Upload
β
SUCCESSWorkManager can also retry failed work.
This gives us durable synchronization.
27. Process Death
This is one of the most important SDE-2 questions.
Interviewer:
"What happens if Android kills your application process while the activity is being recorded?"
A weak answer:
"The ViewModel will restore the state."
That's not enough.
ViewModel state is not durable.
Instead:
Location
β
Persist
β
RoomLocation
β
Persist
β
RoomThen after recreation:
Process recreated
β
Read Room
β
Find active activity
β
Restore stateProcess recreated
β
Read Room
β
Find active activity
β
Restore stateThe important principle is:
Persist critical state before assuming it can survive process death.
28. Background Recording vs UI Lifecycle
Let's consider:
User starts activity
β
Foreground Service
β
Screen locked
β
Activity continuesUser starts activity
β
Foreground Service
β
Screen locked
β
Activity continuesThe UI doesn't need to remain alive.
When the user opens the application again:
Compose
β
ViewModel
β
Observe Room / Tracking State
β
Show current activityCompose
β
ViewModel
β
Observe Room / Tracking State
β
Show current activityThis gives us:
Recording lifecycle
β
UI lifecycleRecording lifecycle
β
UI lifecycleThis separation is one of the most important architectural decisions in the entire design.
29. Battery vs Accuracy
Now the interviewer might challenge:
"High-frequency GPS drains the battery. How would you optimize it?"
This is a trade-off, not a single correct answer.
We need to balance:
High Accuracy
β
Battery ConsumptionHigh Accuracy
β
Battery ConsumptionPotential strategies:
- Configure appropriate location intervals
- Use minimum displacement thresholds
- Ignore low-quality points
- Avoid unnecessary writes
- Batch work where appropriate
- Stop location updates when paused
- Avoid expensive calculations on every UI frame
- Reduce unnecessary map updates
We can encapsulate tracking configuration:
data class TrackingConfig(
val intervalMillis: Long,
val minDistanceMeters: Float,
val priority: Int
)data class TrackingConfig(
val intervalMillis: Long,
val minDistanceMeters: Float,
val priority: Int
)Different activity types can potentially use different configurations.
30. What Happens When the User Pauses?
The state becomes:
RECORDING
β
PAUSEDRECORDING
β
PAUSEDWe should stop counting active movement time.
Depending on product requirements, we may also stop receiving location updates.
The important point is that pause semantics should be explicit.
For example:
Total Duration
=
Active Recording TimeTotal Duration
=
Active Recording Timerather than:
Wall Clock TimeWall Clock Timeif paused duration should be excluded.
This is why defining the domain model before implementation matters.
31. Compose UI Architecture
Now let's return to the UI.
A simplified state:
data class ActivityUiState(
val state: ActivityState = ActivityState.Idle,
val distanceMeters: Double = 0.0,
val durationSeconds: Long = 0L,
val currentSpeed: Float = 0f,
val averageSpeed: Float = 0f,
val pace: Float = 0f,
val route: List<LocationPoint> = emptyList()
)data class ActivityUiState(
val state: ActivityState = ActivityState.Idle,
val distanceMeters: Double = 0.0,
val durationSeconds: Long = 0L,
val currentSpeed: Float = 0f,
val averageSpeed: Float = 0f,
val pace: Float = 0f,
val route: List<LocationPoint> = emptyList()
)Compose observes it:
val state by
viewModel.uiState.collectAsStateWithLifecycle()val state by
viewModel.uiState.collectAsStateWithLifecycle()The important design principle:
Compose renders state; it doesn't own the tracking system.
32. Compose Performance Problem
Now imagine:
GPS update every 2 secondsGPS update every 2 secondsIf every GPS update causes:
Entire screen
β
Recomposition
β
Expensive map renderingEntire screen
β
Recomposition
β
Expensive map renderingwe can have performance problems.
Instead, separate concerns:
ActivityScreen
β
βββ ActivityMetrics
β
βββ ActivityMapActivityScreen
β
βββ ActivityMetrics
β
βββ ActivityMapAnd ensure that frequently changing state doesn't unnecessarily invalidate expensive parts of the UI.
This is where knowledge of Compose becomes relevant in an SDE-2 interview.
The interviewer may not just ask:
"Do you know Compose?"
They may ask:
"How would you prevent frequent location updates from causing unnecessary recomposition?"
That is a much more senior-level question.
33. Route Rendering
The GPS points form:
P1 β P2 β P3 β P4 β P5P1 β P2 β P3 β P4 β P5These can be rendered as a polyline:
P2
/
P1 βββββ P3
\
P4 β P5P2
/
P1 βββββ P3
\
P4 β P5But long activities might have:
10,000+10,000+points.
We shouldn't blindly send huge data structures through every UI update.
Depending on the map SDK and requirements, we can use techniques such as:
- Route simplification
- Incremental updates
- Limiting UI update frequency
- Keeping raw points in storage
- Rendering a simplified representation
This leads to an important distinction:
Raw Route Data
β
Rendered Route DataRaw Route Data
β
Rendered Route DataWe may preserve high-fidelity data for storage while using a lighter representation for rendering.
34. Repository Layer
The repository hides the underlying data sources.
interface ActivityRepository {
suspend fun createActivity(
activity: Activity
)
suspend fun saveLocation(
point: LocationPoint
)
fun observeActivity(
activityId: String
): Flow<Activity>
suspend fun finishActivity(
activityId: String
)
suspend fun uploadActivity(
activityId: String
)
}interface ActivityRepository {
suspend fun createActivity(
activity: Activity
)
suspend fun saveLocation(
point: LocationPoint
)
fun observeActivity(
activityId: String
): Flow<Activity>
suspend fun finishActivity(
activityId: String
)
suspend fun uploadActivity(
activityId: String
)
}The ViewModel doesn't need to know whether the data came from:
Room
Retrofit
Network
CacheRoom
Retrofit
Network
CacheThis is one reason repositories exist.
35. Use Cases
For an SDE-2 design, we can expose meaningful domain operations:
StartActivityUseCase
PauseActivityUseCase
ResumeActivityUseCase
FinishActivityUseCase
ObserveActivityUseCase
UploadActivityUseCaseStartActivityUseCase
PauseActivityUseCase
ResumeActivityUseCase
FinishActivityUseCase
ObserveActivityUseCase
UploadActivityUseCaseFor example:
class StartActivityUseCase(
private val repository: ActivityRepository,
private val trackingController: TrackingController
) {
suspend operator fun invoke() {
repository.createActivity()
trackingController.start()
}
}class StartActivityUseCase(
private val repository: ActivityRepository,
private val trackingController: TrackingController
) {
suspend operator fun invoke() {
repository.createActivity()
trackingController.start()
}
}This makes the domain operations explicit.
But there is an important architectural principle:
Don't introduce layers just because Clean Architecture says so.
If a layer adds no meaningful separation, it may simply add boilerplate.
The simplest architecture that solves the problem is preferable.
36. Complete Recording Flow
Now we can show the complete system:
USER
β
βΌ
Compose Screen
β
βΌ
ViewModel
β
βΌ
Start UseCase
β
βΌ
Tracking Controller
β
βΌ
Foreground Service
β
βΌ
FusedLocationProviderClient
β
βΌ
Location Callback
β
βΌ
Location Processor
β
βββββββββ΄βββββββββ
βΌ βΌ
Metrics Engine Filter
β β
βββββββββ¬βββββββββ
βΌ
Room
β
βΌ
Flow
β
βΌ
ViewModel
β
βΌ
Compose UIUSER
β
βΌ
Compose Screen
β
βΌ
ViewModel
β
βΌ
Start UseCase
β
βΌ
Tracking Controller
β
βΌ
Foreground Service
β
βΌ
FusedLocationProviderClient
β
βΌ
Location Callback
β
βΌ
Location Processor
β
βββββββββ΄βββββββββ
βΌ βΌ
Metrics Engine Filter
β β
βββββββββ¬βββββββββ
βΌ
Room
β
βΌ
Flow
β
βΌ
ViewModel
β
βΌ
Compose UIThen after completion:
Room
β
PENDING
β
WorkManager
β
Upload API
β
Backend
β
SUCCESSRoom
β
PENDING
β
WorkManager
β
Upload API
β
Backend
β
SUCCESS37. Now the Interviewer Starts Challenging the Design
This is where the interview becomes interesting.
A system-design interview isn't:
Question
β
Architecture
β
DoneQuestion
β
Architecture
β
DoneIt is:
Architecture
β
Interviewer challenges assumption
β
You defend / modify design
β
New trade-off
β
Next decisionArchitecture
β
Interviewer challenges assumption
β
You defend / modify design
β
New trade-off
β
Next decisionLet's go through the most important follow-up questions.
38. Interview Question: "Why Foreground Service?"
Answer
Because activity recording is:
- Long-running
- User initiated
- User visible
- Required to continue independently of the UI
The UI lifecycle is insufficient for this requirement.
Therefore:
UI
β
Command
Foreground Service
β
Continuous TrackingUI
β
Command
Foreground Service
β
Continuous Tracking39. Interview Question: "Why Not Keep Everything in the ViewModel?"
Answer
The ViewModel is associated with the UI/navigation lifecycle.
The recording lifecycle should be independent.
If the user:
Locks phone
Leaves screen
Backgrounds appLocks phone
Leaves screen
Backgrounds appthe activity should continue.
Therefore, the ViewModel should coordinate and observe rather than own the long-running tracking operation.
40. Interview Question: "Why Room?"
Answer
Because the activity is valuable user data and must survive:
- Network failure
- UI destruction
- Process recreation
- Temporary synchronization failures
Room gives us durable local persistence and reactive observation through Flow.
41. Interview Question: "Why Offline-First?"
Answer
Because recording an activity shouldn't depend on network availability.
Imagine the user is running through:
Tunnel
Remote Area
Poor Network CoverageTunnel
Remote Area
Poor Network CoverageGPS may still work even though the network doesn't.
We should therefore:
Record locally
β
Sync laterRecord locally
β
Sync laterrather than:
Network unavailable
β
Recording failsNetwork unavailable
β
Recording fails42. Interview Question: "Why WorkManager?"
Answer
Uploading a completed activity is typically deferrable.
The user doesn't necessarily need the upload operation itself to remain active in the foreground.
WorkManager provides:
- Persistent work
- Constraints
- Retry
- Background execution
Therefore:
Recording β Foreground Service
Upload β WorkManagerRecording β Foreground Service
Upload β WorkManager43. Interview Question: "What if Upload Succeeds but the Response Is Lost?"
This is a classic distributed-systems problem.
Suppose:
Client β Server
β
Server successfully stores activity
β
Network timeout
β
Client thinks upload failedClient β Server
β
Server successfully stores activity
β
Network timeout
β
Client thinks upload failedThe client retries.
Now we could accidentally create:
Activity A
Activity AActivity A
Activity ATherefore uploads should be idempotent.
Use a stable:
activityIdactivityIdor an appropriate idempotency mechanism.
For example:
activityId = ABC123activityId = ABC123If the backend receives ABC123 again, it recognizes the duplicate.
44. Interview Question: "What If GPS Gives Completely Wrong Data?"
We shouldn't trust every point.
Use:
Accuracy
Timestamp
Distance
SpeedAccuracy
Timestamp
Distance
Speedto validate points.
For example:
Previous Point
β
Current Point
β
Calculate implied speed
β
Reasonable?
/ \
YES NO
β β
Accept RejectPrevious Point
β
Current Point
β
Calculate implied speed
β
Reasonable?
/ \
YES NO
β β
Accept RejectThe important thing is not the exact threshold.
The important thing is demonstrating that sensor data is noisy input.
45. Interview Question: "What If the User Has 50,000 Points?"
There are two separate concerns.
Storage
Room can handle the points as persisted records.
Rendering
We shouldn't render all raw points indiscriminately.
We can use:
Raw Data
β
Simplification
β
Rendered RouteRaw Data
β
Simplification
β
Rendered RouteWe can also:
- Incrementally update the route
- Reduce UI update frequency
- Avoid copying large lists unnecessarily
- Paginate historical data
46. Interview Question: "What If the Process Dies?"
Persist critical state.
Before:
GPS β MemoryGPS β MemoryAfter:
GPS
β
Process
β
RoomGPS
β
Process
β
RoomThen:
Process dies
β
Process restarts
β
Read Room
β
Recover activityProcess dies
β
Process restarts
β
Read Room
β
Recover activityThis is why persistence is not merely a caching optimization.
It is part of the reliability model.
47. Interview Question: "What If the User Presses Finish Twice?"
This is a concurrency/state-management problem.
Suppose:
Finish
FinishFinish
Finisharrives twice.
We should make the state transition safe:
RECORDING
β
COMPLETEDRECORDING
β
COMPLETEDOnce completed:
COMPLETED β FinishCOMPLETED β Finishshould be a no-op or rejected.
The backend upload should also be idempotent.
This gives protection at both:
Client state level
Backend request levelClient state level
Backend request level48. Interview Question: "What If Two Location Updates Arrive Quickly?"
The processing pipeline should be safe for concurrent updates.
We need to ensure:
Previous PointPrevious Pointis updated atomically with:
Total DistanceTotal DistanceOtherwise we could have race conditions such as:
Update A reads previous point
Update B reads same previous pointUpdate A reads previous point
Update B reads same previous pointleading to incorrect calculations.
The location-processing pipeline should therefore have controlled serialization or synchronization around mutable tracking state.
49. Interview Question: "How Would You Test This?"
I would divide testing into layers.
Unit Tests
DistanceCalculator
PaceCalculator
SpeedCalculator
LocationFilter
ActivityStateMachine
UseCasesDistanceCalculator
PaceCalculator
SpeedCalculator
LocationFilter
ActivityStateMachine
UseCasesExample:
5 km
30 minutes
Expected pace = 6 min/km5 km
30 minutes
Expected pace = 6 min/kmRepository Tests
Test:
Insert activity
Insert points
Read activity
Update upload stateInsert activity
Insert points
Read activity
Update upload stateViewModel Tests
Test:
Idle β Recording
Recording β Paused
Paused β Recording
Recording β CompletedIdle β Recording
Recording β Paused
Paused β Recording
Recording β CompletedIntegration Tests
Test:
Location
β
Processor
β
Room
β
Flow
β
ViewModelLocation
β
Processor
β
Room
β
Flow
β
ViewModelInstrumentation Tests
Test:
Permissions
Foreground Service
Navigation
Process recreationPermissions
Foreground Service
Navigation
Process recreation50. Interview Question: "How Would You Optimize Battery?"
I would first clarify the product requirement.
For example:
"How accurate does the route need to be?"
Because battery optimization is a trade-off.
Potential techniques:
Adaptive location interval
Minimum displacement
Location filtering
Efficient persistence
Avoid unnecessary UI updates
Stop tracking while pausedAdaptive location interval
Minimum displacement
Location filtering
Efficient persistence
Avoid unnecessary UI updates
Stop tracking while pausedThe key is not:
"Always use the highest accuracy."
The key is:
Use the minimum accuracy necessary to satisfy the product requirement.
51. Interview Question: "What If the User Has No Location Permission?"
The flow should be:
Start
β
Check Permission
β
Granted?
/ \
Yes No
β β
βΌ βΌ
Start Request
PermissionStart
β
Check Permission
β
Granted?
/ \
Yes No
β β
βΌ βΌ
Start Request
PermissionWe should distinguish:
Permission deniedPermission deniedfrom:
Permission permanently deniedPermission permanently deniedand guide the user appropriately.
The tracking engine should never assume permission exists.
52. Interview Question: "What Happens When the Activity Is Paused?"
We need to define the semantics.
For example:
Recording
β
PausedRecording
β
PausedDuring pause:
- Active duration should stop
- Distance should stop increasing
- Route should stop updating
- Tracking resources may be reduced/stopped
Then:
Paused
β
Resume
β
RecordingPaused
β
Resume
β
RecordingThe important thing is that the state machine defines the behavior consistently.
53. Interview Question: "Would You Store Every GPS Point Immediately?"
This is a trade-off.
Writing every point individually may create:
Many database operations
β
Battery / I/O costMany database operations
β
Battery / I/O costBut batching too aggressively increases the risk of losing recent points.
So I would consider:
Batching
+
Periodic persistence
+
Durability requirementsBatching
+
Periodic persistence
+
Durability requirementsThe correct choice depends on the product's tolerance for data loss and expected location frequency.
In an interview, I would explicitly state the trade-off rather than claiming one universal solution.
54. Interview Question: "Would You Calculate Metrics on the Backend?"
For live activity metrics:
Distance
Speed
PaceDistance
Speed
Pacethe device should calculate them locally because the user needs immediate feedback.
Backend processing can still be used for:
Post-processing
Analytics
Leaderboards
Advanced statisticsPost-processing
Analytics
Leaderboards
Advanced statisticsSo:
Real-time metrics
β
Client
Heavy / authoritative processing
β
BackendReal-time metrics
β
Client
Heavy / authoritative processing
β
BackendThis reduces latency during the recording experience.
55. Interview Question: "How Would You Support Running and Cycling?"
Don't create:
RunningTrackingService
CyclingTrackingService
WalkingTrackingServiceRunningTrackingService
CyclingTrackingService
WalkingTrackingServiceInstead:
Tracking Engine
β
βββββββββββββΌββββββββββββ
βΌ βΌ βΌ
Running Cycling WalkingTracking Engine
β
βββββββββββββΌββββββββββββ
βΌ βΌ βΌ
Running Cycling WalkingThe common engine handles:
Location
Distance
Persistence
LifecycleLocation
Distance
Persistence
LifecycleActivity-specific policies can handle:
Pace
Speed
CaloriesPace
Speed
CaloriesThis improves extensibility.
56. Interview Question: "Would You Use Clean Architecture?"
This is a trickier question.
I wouldn't answer:
"Yes, because Clean Architecture is best practice."
Instead:
"I would separate presentation, domain and data responsibilities where those boundaries provide value. I don't want to introduce layers purely for ceremony."
For example:
UI
β
ViewModel
β
Use Case
β
Repository
β
Data SourcesUI
β
ViewModel
β
Use Case
β
Repository
β
Data Sourcesmakes sense if the domain logic and data sources are sufficiently complex.
But:
UI
β
ViewModel
β
UseCase
β
Interactor
β
Manager
β
Repository
β
DataProviderUI
β
ViewModel
β
UseCase
β
Interactor
β
Manager
β
Repository
β
DataProviderwithout meaningful responsibility boundaries is overengineering.
The goal is:
The simplest architecture that handles the current complexity and expected change.
57. Final Architecture
After all the discussions, the final architecture becomes:
βββββββββββββββββββββββ
β Compose UI β
β β
β Recording / History β
β Activity Detail β
ββββββββββββ¬βββββββββββ
β
βΌ
βββββββββββββββββββββββ
β ViewModel β
ββββββββββββ¬βββββββββββ
β
βΌ
βββββββββββββββββββββββ
β UseCases β
ββββββββββββ¬βββββββββββ
β
βββββββββββββββββββ΄ββββββββββββββββββ
β β
βΌ βΌ
ββββββββββββββββββββββ ββββββββββββββββββββββ
β Tracking Controllerβ β ActivityRepository β
βββββββββββ¬βββββββββββ ββββββββββββ¬ββββββββββ
β β
βΌ βββββββ΄ββββββ
ββββββββββββββββββββββ βΌ βΌ
β Foreground Service β Room Retrofit
βββββββββββ¬βββββββββββ
β
βΌ
ββββββββββββββββββββββ
β Location Provider β
βββββββββββ¬βββββββββββ
β
βΌ
ββββββββββββββββββββββ
β Location Processor β
βββββββββββ¬βββββββββββ
β
ββββββββ΄ββββββββ
βΌ βΌ
LocationFilter Metrics
β β
ββββββββ¬ββββββββ
βΌ
Room
β
βΌ
Flow
β
βΌ
ViewModel
β
βΌ
Compose
Completed Activity
β
βΌ
Room
β
βΌ
WorkManager
β
βΌ
Upload API
β
βΌ
Backendβββββββββββββββββββββββ
β Compose UI β
β β
β Recording / History β
β Activity Detail β
ββββββββββββ¬βββββββββββ
β
βΌ
βββββββββββββββββββββββ
β ViewModel β
ββββββββββββ¬βββββββββββ
β
βΌ
βββββββββββββββββββββββ
β UseCases β
ββββββββββββ¬βββββββββββ
β
βββββββββββββββββββ΄ββββββββββββββββββ
β β
βΌ βΌ
ββββββββββββββββββββββ ββββββββββββββββββββββ
β Tracking Controllerβ β ActivityRepository β
βββββββββββ¬βββββββββββ ββββββββββββ¬ββββββββββ
β β
βΌ βββββββ΄ββββββ
ββββββββββββββββββββββ βΌ βΌ
β Foreground Service β Room Retrofit
βββββββββββ¬βββββββββββ
β
βΌ
ββββββββββββββββββββββ
β Location Provider β
βββββββββββ¬βββββββββββ
β
βΌ
ββββββββββββββββββββββ
β Location Processor β
βββββββββββ¬βββββββββββ
β
ββββββββ΄ββββββββ
βΌ βΌ
LocationFilter Metrics
β β
ββββββββ¬ββββββββ
βΌ
Room
β
βΌ
Flow
β
βΌ
ViewModel
β
βΌ
Compose
Completed Activity
β
βΌ
Room
β
βΌ
WorkManager
β
βΌ
Upload API
β
βΌ
Backend58. The Most Important Design Decisions
If I had to summarize the entire interview in a few decisions:
Decision 1
Foreground Service for active recording
Because recording must survive the UI lifecycle.
Decision 2
Room for durable local activity data
Because network and process state are unreliable.
Decision 3
Offline-first recording
Because GPS recording shouldn't depend on internet connectivity.
Decision 4
WorkManager for deferred synchronization
Because completed activities can be uploaded asynchronously.
Decision 5
Incremental location processing
Because recalculating an entire route for every point is wasteful.
Decision 6
Explicit activity state machine
Because Start/Pause/Resume/Finish are state transitions.
Decision 7
Separate tracking from UI
Because:
UI lifecycle β Recording lifecycleUI lifecycle β Recording lifecycleDecision 8
Battery-aware location configuration
Because location accuracy always has a cost.
59. What Makes This an SDE-2 Answer?
A junior answer might sound like:
"I'll use Compose, MVVM, Room, Retrofit and a Foreground Service."
That's technology-driven.
An SDE-2 answer sounds like:
"The first requirement I want to clarify is whether recording must continue when the application goes into the background. Assuming it does, I need to separate the recording lifecycle from the UI lifecycle. I'll therefore use a Foreground Service for active tracking. Since the activity must survive network failures and potentially process recreation, I'll persist the recording locally in Room. Once the activity is completed, I'll mark it as pending synchronization and use WorkManager to upload it with retry and idempotency."
Notice the difference.
The second answer isn't impressive because it mentions more technologies.
It's impressive because every technology is connected to a requirement.
60. A Rule I Use in System Design Interviews
Whenever I introduce a component, I should be able to answer:
"What problem does this component solve?"
For example:
Foreground Service
Problem:
Continuous user-visible background tracking
Solution:
Foreground ServiceProblem:
Continuous user-visible background tracking
Solution:
Foreground ServiceRoom
Problem:
Durable local activity state
Solution:
RoomProblem:
Durable local activity state
Solution:
RoomWorkManager
Problem:
Reliable deferred synchronization
Solution:
WorkManagerProblem:
Reliable deferred synchronization
Solution:
WorkManagerRepository
Problem:
Separate business logic from data sources
Solution:
RepositoryProblem:
Separate business logic from data sources
Solution:
RepositoryUse Case
Problem:
Encapsulate meaningful domain operations
Solution:
Use CaseProblem:
Encapsulate meaningful domain operations
Solution:
Use CaseState Machine
Problem:
Prevent invalid activity transitions
Solution:
Explicit statesProblem:
Prevent invalid activity transitions
Solution:
Explicit statesThis is the mindset interviewers are looking for.
61. Common Mistakes in This Interview
Mistake 1 β Starting With Technologies
Bad:
"I'll use Compose, Hilt, Room and Retrofit."
Better:
"First I want to understand whether tracking needs to survive the UI lifecycle."
Mistake 2 β Designing Everything
Trying to design:
Feed
Profile
Messaging
Challenges
Notifications
Analytics
PaymentsFeed
Profile
Messaging
Challenges
Notifications
Analytics
Paymentsbefore solving recording.
Instead:
Identify the critical user journey first.
Mistake 3 β Ignoring Background Execution
If location tracking is the core feature and the answer doesn't discuss Android background execution, the design is incomplete.
Mistake 4 β Ignoring Battery
Continuous GPS has a cost.
A senior Android engineer should bring battery considerations into the conversation naturally.
Mistake 5 β Ignoring Process Death
Anything important that exists only in memory is a reliability risk.
Mistake 6 β Assuming Perfect Network
Mobile applications operate in:
Airplanes
Tunnels
Elevators
Remote areas
Poor networks
Network transitionsAirplanes
Tunnels
Elevators
Remote areas
Poor networks
Network transitionsOffline behavior should be part of the design.
Mistake 7 β Only Discussing Happy Paths
Don't stop at:
Start β Track β FinishStart β Track β FinishAsk:
What if GPS fails?
What if permission is denied?
What if the network disappears?
What if the process dies?
What if upload fails?
What if upload succeeds but response is lost?
What if Finish is tapped twice?What if GPS fails?
What if permission is denied?
What if the network disappears?
What if the process dies?
What if upload fails?
What if upload succeeds but response is lost?
What if Finish is tapped twice?That's where SDE-2-level reasoning starts becoming visible.
62. How I Would Present This in a Real Interview
If I had approximately 30β40 minutes, I wouldn't spend the entire interview drawing every class.
I'd structure my conversation like this:
First 5 minutes
Clarify:
Users
Critical journey
Background requirement
Offline requirement
ScaleUsers
Critical journey
Background requirement
Offline requirement
ScaleNext 5 minutes
Define scope and draw:
UI
β
ViewModel
β
Tracking
β
Service
β
LocationUI
β
ViewModel
β
Tracking
β
Service
β
LocationNext 10 minutes
Deep dive into:
Location processing
Room
Offline-first
Process death
BatteryLocation processing
Room
Offline-first
Process death
BatteryNext 10 minutes
Let the interviewer challenge:
GPS accuracy
Upload failure
Duplicate upload
50k points
Permissions
ConcurrencyGPS accuracy
Upload failure
Duplicate upload
50k points
Permissions
ConcurrencyFinal few minutes
Discuss:
Testing
Monitoring
Trade-offs
Future extensionsTesting
Monitoring
Trade-offs
Future extensionsThis keeps the conversation focused.
63. The 2-Minute Interview Answer
If the interviewer says:
"Give me a quick overview of your design."
I would answer:
"I'd first clarify whether activity recording needs to continue in the background and whether it needs to work offline. Assuming both are required, I'd separate the recording lifecycle from the UI lifecycle.
The Compose UI communicates with a ViewModel, but the ViewModel doesn't own location tracking. I'd use a Foreground Service for the active recording session and FusedLocationProviderClient for location updates.
Each location update would go through a processing layer that validates GPS accuracy and incrementally calculates distance, speed and pace. The active activity and route points would be persisted locally using Room so recording doesn't depend on the network and can recover from process recreation.
The UI would observe local state through Flow/StateFlow rather than depending directly on the location callback.
When the user finishes the activity, I'd mark it as pending synchronization. WorkManager would upload the activity when network connectivity is available, with retry and an idempotent activity ID to prevent duplicate uploads.
The main trade-offs I'd discuss are location accuracy versus battery consumption, Foreground Service versus WorkManager, how frequently we persist location points, and how we prevent frequent GPS updates from causing expensive Compose recomposition or map rendering."
That's a much stronger answer than simply listing:
MVVM
Clean Architecture
Room
Retrofit
WorkManager
Foreground ServiceMVVM
Clean Architecture
Room
Retrofit
WorkManager
Foreground Servicebecause it explains why each component exists.
64. Final Takeaway
The biggest lesson from this system-design problem isn't how to build a GPS tracker.
It's how to approach an ambiguous Android problem.
Start with:
Requirements
β
Critical User Journey
β
Constraints
β
Hardest Problem
β
Architecture
β
Data Flow
β
Failure Scenarios
β
Trade-offsRequirements
β
Critical User Journey
β
Constraints
β
Hardest Problem
β
Architecture
β
Data Flow
β
Failure Scenarios
β
Trade-offsNot:
Compose
β
MVVM
β
Room
β
Retrofit
β
DoneCompose
β
MVVM
β
Room
β
Retrofit
β
DoneA strong SDE-2 engineer doesn't begin by asking:
"Which Android component should I use?"
They begin by asking:
"What problem am I trying to solve, what constraints matter, and what happens when things go wrong?"
Once those questions are answered, the technology choices become much easier.
For this problem:
Continuous user-visible tracking
β
Foreground Service
Durable local state
β
Room
Offline recording
β
Local-first
Deferred synchronization
β
WorkManager
Reactive UI
β
Flow / StateFlow
Frequent sensor updates
β
Controlled UI updates
Activity lifecycle
β
State MachineContinuous user-visible tracking
β
Foreground Service
Durable local state
β
Room
Offline recording
β
Local-first
Deferred synchronization
β
WorkManager
Reactive UI
β
Flow / StateFlow
Frequent sensor updates
β
Controlled UI updates
Activity lifecycle
β
State MachineThat is the real system design.
The technologies are simply the implementation of those decisions.
And that is ultimately what an SDE-2 system-design interview is evaluating:
Not whether you know more components, but whether you can make the right engineering decisions under constraints.
If you made it till here then Thank You for reading. Feel free to add your thoughts to this. A System Design Interview doesn't have a fix answer. It's more about having a discussion with Interviewer.
Connect with me on LinkedIn: https://www.linkedin.com/in/ninadbhase/