System Design Fundamentals for Android Engineers
System Design Isn't Just a Backend Discipline
"System design" usually conjures load balancers and database sharding. But a production Android app is a distributed system in its own right — it has its own data layer, its own caching strategy, its own consistency model between local storage and a remote source of truth. Client-side system design is a real skill, and it's increasingly what separates a senior Android engineer from a mid-level one.
The core layers, and what each one owns
- Presentation layer — Compose UI + ViewModels. Owns nothing but how state is displayed and how user intent becomes an event.
- Domain layer — use cases that encode business rules independent of Android or network frameworks. This is what stays stable while everything around it changes.
- Data layer — repositories that decide, per request, whether to serve from cache, network, or both. This is where most system design decisions actually live.
The single most important decision: where's the source of truth?
The pattern that scales best for most apps: local database as the single source of truth. The UI never reads network responses directly — it observes a Room (or SQLDelight) query as a Flow. The network layer's only job is to fetch fresh data and write it into that same database. The UI updates automatically because it's watching the DB, not the network call.
fun observeFeed(): Flow> = postDao.observeAll()
suspend fun refreshFeed() {
val fresh = api.getFeed()
postDao.upsertAll(fresh.map { it.toEntity() })
// UI updates automatically — it's observing postDao.observeAll()
}This single pattern eliminates a huge class of bugs: stale UI after a background refresh, race conditions between two in-flight requests, and duplicated "loading" state logic scattered across screens.
Caching strategy: pick a policy, don't wing it
| Strategy | When to use |
|---|---|
| Cache-then-network | Show stale data instantly, refresh silently — best for feeds, dashboards |
| Network-then-cache | Correctness matters more than speed — checkout flows, balances |
| Cache-only with manual refresh | Expensive-to-fetch data the user explicitly controls (e.g. "sync now") |
Worked example: designing a social feed client
Say the question is "design the Android client for a Twitter-like feed." A strong answer walks through:
- Data model — a paginated list of posts, each with author, media, engagement counts, and a local "pending sync" flag for optimistic actions like likes.
- Pagination — Paging 3 library backed by a
RemoteMediator, which fetches pages from the network and writes them into Room, while the UI observes aPagingSourceoff that same table. - Optimistic updates — a "like" writes to the local DB immediately (instant UI feedback), fires the network request in the background, and rolls back only on failure.
- Image loading — Coil or Glide with disk + memory caching, sized requests (never load a 4000px image into a 200dp thumbnail), and placeholder/error states baked into the loading contract, not bolted on per-screen.
- Background refresh — WorkManager for periodic sync when the app isn't in the foreground, with constraints (unmetered network, battery not low) so refresh doesn't drain the user's data plan or battery.
Trade-offs worth naming out loud
- Consistency vs. responsiveness — optimistic UI feels great but needs a rollback story for every mutation, or users lose trust in the app the first time a "like" silently disappears.
- Cache staleness vs. battery/data usage — aggressive refresh keeps data fresh but costs battery and mobile data; the right interval depends on how time-sensitive the content actually is.
- Module boundaries vs. build times — a heavily modularized app scales better for large teams working in parallel, but adds real Gradle configuration overhead that only pays off past a certain team size.
None of these have a universally correct answer — which is exactly why system design questions are open-ended. The goal isn't reciting the "right" architecture; it's showing you understand what each decision actually costs.