A practical framework for iOS & mobile engineers interviewing at Senior, Staff, and Lead level — covering the mobile-specific system design ground that most interview prep content, built for backend distributed systems, quietly skips.
If this saves you prep time, a ⭐ star helps other mobile engineers find it. If you've run one of the practice problems as a real mock interview, PRs adding your own worked example are very welcome — see Contributing.
- Why This Exists
- The Universal 7-Step Framework
- 8 Domains Interviewers Keep Coming Back To
- Worked Example: Offline-First Notes App
- Tradeoff Cheat Sheet
- 8 Practice Problems
- Self-Scoring Checklist
- Related Reading
- Contributing
- License
Search "system design interview" and you'll get a hundred writeups of a URL shortener, a rate limiter, or a Twitter timeline — all backend, distributed-systems thinking. Useful, but it isn't what a mobile system design round actually tests.
A mobile round tests whether you can reason about a client that goes offline mid-task, a release you can't hotfix once it's in the App Store, a battery and data budget the backend never has to think about, and a local data store that has to reconcile with a server it can't always reach. That's a genuinely different design muscle.
| Question it answers | You fully control the system? | |
|---|---|---|
| Backend system design | How do I scale a service, shard a database, design an API many unknown clients call? | Yes — end to end |
| Mobile system design | How do I design a client that goes offline, runs on a battery, and can't be patched instantly? | No — the client lives outside your infra |
Use this structure out loud, every time. Interviewers aren't grading whether you land on their preferred answer — they're grading whether you have a repeatable process.
- Clarify requirements — functional (what must it do) and non-functional (scale, latency, offline support, battery/data constraints). Mobile non-functionals differ from backend ones — say so explicitly.
- Define scope explicitly — state what you're excluding out loud: "I'll skip auth and focus on feed rendering and caching." This is what keeps a 45-minute round focused.
- High-level architecture — draw client, API layer, backend services, data stores. Keep it rough; this is a map, not the deep dive.
- Deep dive into 2–3 components — let the interviewer steer, but propose where you think the interesting complexity lives. That proposal is itself a signal of judgment.
- Data model / API contract — for mobile-specific rounds, this means the local cache schema and the sync strategy, not just a server-side ERD.
- Address scale & edge cases — poor connectivity, large payloads, pagination, conflict resolution, cache invalidation. This is where mobile rounds diverge hardest from backend ones.
- State tradeoffs, explicitly — "I chose X over Y because of Z; this costs us W." Force yourself to state at least three before calling a problem "done" — it's the single biggest gap between a mid-level and a senior answer.
Almost every mobile system design prompt — feed, chat, notes app, video player — is a remix of these eight. Learn the domain once, and you can answer any prompt built on top of it.
| # | Domain | What it covers |
|---|---|---|
| 1 | Offline-first architecture | Local-first store, sync engine, conflict resolution (last-write-wins vs. CRDTs), queuing writes made while offline |
| 2 | Image / media pipeline | Multi-tier caching, progressive loading, prefetching, LRU eviction, adaptive quality for poor networks |
| 3 | Feed & pagination | Cursor vs. offset pagination, merging cache with fresh network data without UI flicker, deduplication |
| 4 | Push notification architecture | APNs flow, silent pushes for background sync, notification-driven deep links, token refresh/rotation |
| 5 | Real-time features | WebSocket vs. long polling vs. SSE, reconnect/backoff strategy, message ordering and delivery guarantees |
| 6 | Analytics / telemetry pipeline | Batching, sampling, offline queuing, PII scrubbing, event schema versioning across releases |
| 7 | Modularized app architecture | Structuring a large app's dependency graph, on-demand resources, build time strategy at 30+ modules |
| 8 | A/B testing & feature flags | Flag systems designed around App Store review lag — "you can't hotfix a native app" is the real constraint |
How to study this list: don't memorize eight isolated topics. Most prompts stack two or three — a chat app is Real-time + Sync + Push; a feed is Feed + Media + Telemetry. Once a domain is solid, you'll recognize it inside almost any prompt an interviewer hands you.
The framework applied end-to-end. Practice saying this out loud in under 20 minutes before adding your own detail.
Step 1 · Clarify
Functional: create/edit/delete notes offline, sync across a user's devices, resolve conflicts when two devices edit the same note offline. Non-functional: edits must feel instant (no spinner on save), sync should be near-real-time when online, and the app must never silently lose a user's writing.
Step 2 · Scope
In scope: local storage, sync engine, conflict resolution. Out of scope: auth, sharing/collaboration, rich media attachments — state this explicitly and move on.
Step 3 · High-level architecture
- Local store — on-device database (SQLite/Core Data) is the source of truth for the UI; every read and write hits it first, never the network directly.
- Change log / outbox — every local mutation is appended to an ordered, durable queue before it's applied, so the sync engine always knows what still needs to travel upstream.
- Sync engine — a background component that drains the outbox when connectivity is available, pulls remote changes, and applies conflict resolution before writing back to the local store.
- Backend — stores authoritative notes plus a per-note revision/version, and exposes a delta endpoint ("give me everything changed since revision N") rather than requiring a full re-sync every time.
Step 4 · Deep dive — conflict resolution
The interesting complexity, and where most candidates get vague. Two real devices edit the same note while both are offline — the naive fix, last-write-wins by timestamp, silently discards one device's edits and requires clock synchronization mobile devices don't reliably have.
The stronger answer: keep a per-field or per-block revision, and only fall back to last-write-wins at the whole-note level as an explicit, user-visible "keep both versions" prompt — never a silent overwrite. Full CRDT-based merge is the "gold standard" for a notes app specifically (text CRDTs are a well-studied problem), but note the added engineering cost and only propose it if the interviewer wants to go there.
Step 5 · Data model / sync strategy
struct Note {
let id: UUID
var body: String
var revision: Int // keys the delta endpoint & conflict check
var dirty: Bool // lets the UI show "syncing…" without blocking input
var lastModified: Date
}
struct OutboxEntry {
let noteId: UUID
let opType: SyncOpType // .create / .update / .delete
let payload: Data
let createdAt: Date
}Step 6 · Scale & edge cases
- Partial connectivity — sync in small batches with retry/backoff, not one giant transaction that fails entirely on a dropped connection.
- Multi-device race — two devices coming online at the same moment both push; the backend, not either client, is the tie-breaker on revision order.
- Storage growth — outbox entries are pruned once acknowledged by the backend; never let the change log grow unbounded on-device.
Step 7 · Tradeoffs — state at least three
| Tradeoff | Cost |
|---|---|
| Local-first vs. server-first source of truth | Local-first keeps editing instant offline, at the cost of needing a conflict resolution system a server-first design wouldn't |
| Field-level revisions vs. last-write-wins | Field-level never silently drops content, at the cost of real engineering complexity and a bigger sync payload |
| Delta sync vs. full re-sync | Delta sync is far cheaper on data/battery, at the cost of needing a durable, monotonic revision counter the backend must never skip or reuse |
Expect these follow-ups
- "What if a user has five devices, not two?" — your tie-breaking logic and revision counter need to hold under N-way conflicts, not just two.
- "How do you evolve the sync payload format later without breaking old app versions still in the wild?" — the App-Store-lag constraint again.
- "What's your rollback plan if the sync engine ships a bug that corrupts local data?" — a lead-level answer names a safeguard (versioned local backups, a kill switch) before being asked.
The pattern to internalize: every mobile system design answer eventually reduces to "where does the source of truth live, and how do we reconcile it when the client was offline." Once you can defend that decision under follow-up questions, most prompts in this space become variations on the same walkthrough.
Reusable across almost every mobile system design prompt. Use it as a checklist at Step 7 of the framework — pick the two or three rows that actually apply, and say the "pick B when" column out loud as your justification.
| Decision | Option A | Option B | Pick B when… |
|---|---|---|---|
| Conflict resolution | Last-write-wins (simple, can silently drop edits) | CRDT / field-level merge (safe, real engineering cost) | Content is collaborative or high-value (documents, notes) |
| Pagination | Offset-based (simple, breaks under fast-moving feeds) | Cursor-based (stable under inserts/deletes) | The feed changes while the user scrolls |
| Image cache eviction | Time-based expiry (simple, wastes storage) | LRU with a size cap (predictable footprint) | Storage/battery budget is a stated constraint |
| Real-time transport | Long polling (simple, higher latency/battery cost) | WebSocket / SSE (lower latency, more infra) | Sub-second delivery is a real product requirement |
| Rollout mechanism | Ship in the binary (no flag infra needed) | Server-driven feature flag | You need to kill/phase a feature without an App Store release |
| Sync trigger | Silent push (near-instant, needs reliable APNs delivery) | Periodic background fetch (reliable, higher latency) | APNs delivery can't be guaranteed for the use case |
| Local storage engine | Core Data (mature, ships with migrations/CloudKit hooks) | Raw SQLite / wrapper (lighter, full query control) | You need fine-grained query performance or cross-platform reuse |
| Delivery guarantee | At-most-once (simpler, can silently lose a message) | At-least-once + client-side dedup (safer, needs idempotency) | Message loss is unacceptable (chat, payments, orders) |
Run these as timed 35–45 minute mocks. Talk out loud, use the framework above, and force yourself to state at least three explicit tradeoffs before calling one "done." Check them off as you complete a mock.
- Instagram's feed — caching, pagination, offline viewing, media loading.
- A chat application's mobile client — delivery guarantees, offline queue, sync on reconnect, read receipts.
- An offline-first task app — multi-device sync and conflict resolution (contrast with the notes-app walkthrough above).
- An in-house image loading library — and when you'd adopt Kingfisher/SDWebImage instead.
- A video streaming client — adaptive bitrate awareness, prefetching next segments, download-for-offline.
- Crash-free rollout infrastructure — feature flags, phased rollout, automatic rollback on crash rate.
- Local storage for 10M+ rows of user data — schema, migration strategy, query performance.
- Push-notification-driven background sync — silent push → background fetch → local DB update → UI refresh.
Rate each mock honestly before moving to the next one:
- Did you state your scope exclusions out loud before drawing anything?
- Did you reach for the local cache schema and sync strategy, not just a server-side data model?
- Did you name at least three explicit tradeoffs without being prompted for them?
- Could you defend your answer against a "what if this needed to scale 10x" follow-up?
- Did you finish inside the 35–45 minute window with time left for questions?
Prioritized roughly best-to-good — official/primary sources first since they age better than SEO content:
- weeeBox/mobile-system-design — a free, structured framework covering persistence, repository pattern, image loading, DI graphs, and navigation patterns with worked examples.
- Point-Free (pointfree.co) — the primary source for The Composable Architecture, useful if a prompt pushes toward TCA-flavored state management.
- objc.io's "Architecture" writings — the original vendor-neutral treatment of MVC/MVVM/VIPER tradeoffs, useful background for the "how would you structure the presentation layer" half of a mobile system design round.
- Martin Kleppmann's writing/talks on CRDTs and local-first software — if you want to go past blog-level treatment of the conflict-resolution deep dive above.
This repo focuses specifically on the system design track. If you also want Swift concurrency, architecture patterns (MVVM/VIPER/TCA/Clean Architecture), DDD, and dependency injection prep in the same style, that's a companion track — ping me / check the pinned post on my profile for the latest.
This is meant to grow. If you've run one of the 8 practice problems as a real mock interview and want to add your own worked example step-by-step format as the notes-app walkthrough), open a PR. A few ground rules:
- Follow the existing 7-step structure so the format stays consistent.
- State tradeoffs explicitly — that's the whole point of this repo.
- Keep it mobile-specific — if the answer would be identical for a backend service, it probably belongs somewhere else.
Typo fixes, additional tradeoff-table rows, and additional practice problems are all welcome too.