12.9 — 5 iOS System Design Scenarios
How system design interviews work
System design at the senior iOS level isn’t about whiteboarding databases — it’s about the iOS half of a distributed system. Expect 45–60 minutes:
- Clarify scope (5 min): users, scale, platforms, must-have vs nice-to-have.
- High-level architecture (10 min): client + server boundary, data shape, caching strategy.
- Deep dive on iOS (20 min): screens, data flow, offline behavior, edge cases.
- Tradeoffs + scale (10 min): what breaks at 10×, what you’d add at 100×.
- Wrap-up (5 min): summarize the design, name what you’d want to learn more about.
The interviewer is listening for: explicit tradeoffs, asking the right clarifying questions, knowing iOS-specific patterns (background tasks, push notifications, offline), and managing time across the discussion.
Five canonical prompts follow. Treat them as scripts — practice each out loud in 45 min.
Scenario 1 — Instagram Feed
Clarifying questions
- How many users? (assume 100M MAU, 10M DAU)
- Feed source? (people you follow + algorithmic suggestions)
- Media types? (image + short video)
- Realtime requirements? (no — eventual consistency is fine; new posts appear within 30s)
- Offline? (read cached feed; posting queues for retry)
High-level architecture
[iOS app] ──HTTPS──> [Feed API] ──> [Feed Service + Ranker]
──> [Media CDN]
[iOS app] ──HTTPS──> [Post API] ──> [Storage + Notification fan-out]
iOS architecture
Data layer:
- Local cache: SwiftData with
Postmodel (id, authorId, mediaURL, caption, likeCount, timestamp). - Server-driven page size: API returns cursor + N items. Default N=20.
- Image cache: 2-tier (NSCache memory + URLCache disk + CDN).
View layer:
LazyVStackinsideScrollView, withForEach(posts, id: \.id).- Each post row is
Equatableto skip redundant re-renders. - Prefetch +5 ahead on scroll via
.task(id: post.id)per visible row.
State:
FeedViewModel(@Observable) withstate: enum { idle, loading, loaded([Post], cursor), error }.- Pull-to-refresh via
.refreshable.
Concurrency:
- Feed fetch:
await api.feed(after: cursor). - Image decode: async on
Task.detached, downsampled to display size. - Optimistic like: increment
likeCountlocally, fireawait api.like(postId); revert on failure.
Critical iOS-specific concerns
- First-paint perceived latency: render cached feed instantly; refresh in background.
- Memory pressure during scroll: cap NSCache to 50 images; downsample on decode.
- Cell reuse: SwiftUI handles automatically; in UIKit,
prepareForReusecancels image load. - Background refresh:
BGAppRefreshTaskprefetches new posts when system allows. - Push notifications: silent push triggers
feed-refresh-availablebadge.
Tradeoffs to mention
- Server-driven pagination (cursor) vs client-driven (offset): cursor wins for stability.
- Inline video autoplay: drains battery; default to play-on-visible-and-Wi-Fi.
- Real-time updates: poll-on-foreground is simpler than WebSocket for feeds.
At 10× scale
- CDN regionalization.
- Image format negotiation (HEIC/AVIF where supported).
- Ranker on-device with Core ML for personalized re-ranking of cached feed.
Scenario 2 — Real-Time Chat (WhatsApp / iMessage)
Clarifying questions
- 1:1 only, or group? (both)
- Media types? (text + image + voice + file)
- End-to-end encryption? (yes — assume Signal protocol)
- Offline behavior? (read history offline; queue outgoing for retry)
- Multi-device sync? (yes — same account on phone + iPad)
High-level architecture
[iOS] ──WebSocket──> [Chat Gateway] ──> [Message Service]
──> [Media uploader → CDN]
──> [E2EE key server]
[iOS] ←─APNs────── [Notification Fan-out]
iOS architecture
Persistence:
- SQLite (via SwiftData or GRDB) with tables:
Conversations,Messages,Attachments. - Messages keyed by
(conversationId, serverId, clientId). Indexed on(conversationId, timestamp DESC).
Network:
URLSessionWebSocketTaskfor live messages; auto-reconnect with exponential backoff.- Background
URLSessionfor large media uploads. - HTTP for history fetch (paginated by time cursor).
State:
ChatViewModelper conversation; subscribes to AsyncStream from a centralConnectionManager.- Outgoing messages go through a queue actor with retry logic.
Crypto:
- Signal protocol — keys stored in Keychain with biometric protection.
- Each message encrypted client-side before send; server only sees ciphertext.
Critical iOS-specific concerns
- Background message delivery: APNs with
mutable-content+ Notification Service Extension to decrypt before display. - Energy: WebSocket connection only when app is foreground or recently used; APNs handles background.
- Multi-device: Each device has its own keypair; sender encrypts per-recipient-device.
- Voice notes: record with
AVAudioRecorder; play withAVAudioPlayer+ audio session category.playback. - Read receipts: send when message appears on screen (Time Profiler — don’t fire from scroll without debounce).
- Typing indicators: throttle to 1/sec; cancel on send.
Tradeoffs
- WebSocket vs APNs polling: hybrid is mandatory — WebSocket for foreground latency, APNs for background reliability.
- Local-first vs server-of-truth: messages local-first with server as durable backup; conflict resolution via server timestamp.
- Group fan-out: client-fan-out (sender encrypts per-recipient) is more private but burns more uplink than server-fan-out (server holds plain copies briefly).
At 10× scale
- Sharded gateways by user ID.
- Lazy decryption of group messages (decrypt only when displayed).
- Sync offload via
BGProcessingTaskovernight.
Scenario 3 — Offline-First Note App (Bear / Notion mobile)
Clarifying questions
- Sync across devices? (yes — iOS + iPad + Mac)
- Conflict resolution? (last-write-wins is acceptable; CRDT preferred for collaboration)
- Backend? (vendor’s own — assume CloudKit-style)
- Search? (full-text across all notes, instant)
High-level architecture
[iOS] ──optimistic──> [Local SwiftData store]
↓ sync
[Sync engine] ──> [Server]
↑ pull
[Mac/iPad] ─────────────────────────── [Server]
iOS architecture
Persistence:
- SwiftData with
Note(id, title, body, modifiedAt, deletedAt, syncState). - Soft-delete via
deletedAtfor tombstone-based sync. - Full-text search via SQLite FTS5 (
@Modelwith custom predicate).
Sync engine:
- Bidirectional with vector clocks or HLC (Hybrid Logical Clock) per device.
- Pull: fetch changes since last sync token.
- Push: send local changes since last sync token.
- Conflict: server-merge or last-write-wins; surface conflict via “Two versions exist” UI.
State:
- Always read from local store; UI never blocks on network.
SyncStatusobservable showing “Synced” / “Syncing” / “Offline”.
Critical iOS-specific concerns
- Editor performance: debounced auto-save (every 2 s of inactivity).
- Background sync:
BGAppRefreshTaskon app suspend; trigger immediately on foreground. - Spotlight: index notes via
CSSearchableItemfor system-wide search. - iCloud Drive support: optional — store notes as files in
~/Library/Mobile Documents/iCloud~com.acme.notes. - Universal Clipboard: copy-paste works across devices via system.
- Handoff: continue editing across devices via
NSUserActivity. - Widgets: today’s note via
WidgetKit.
Tradeoffs
- CRDT vs LWW: CRDT is invisible-merge magic but heavier to implement; LWW is trivial but loses one user’s edit.
- SwiftData vs Core Data: SwiftData easier for new code, Core Data battle-tested for sync edge cases.
- CloudKit vs custom backend: CloudKit is free and zero-config but Apple-only; custom backend enables Android/web.
At 10× scale
- Lazy-load body for very large notebooks (load metadata only, body on tap).
- Differential sync (send only changed lines, not full body).
- E2EE option for paid users — keys derived from passphrase.
Scenario 4 — Navigation / Maps App
Clarifying questions
- Pedestrian + driving? (both)
- Offline maps? (yes — region download)
- Real-time traffic? (yes — server-driven)
- ETA accuracy? (within 10 % of actual time)
High-level architecture
[iOS] ──> MapKit ──> [Apple Maps tiles]
[iOS] ──> [Routing API]
[iOS] ←─ APNs ←─ [Traffic incident push]
iOS architecture
Map rendering:
MapKitMap { }SwiftUI view (iOS 17+) withMapPolylinefor route.- Custom tile overlay for offline regions (cached as
MKTileOverlay).
Location:
CLLocationManagerwithkCLLocationAccuracyBestForNavigationduring turn-by-turn.- Background updates enabled; geofence-based wake for nav resumption.
Routing:
- Online:
MKDirections(Apple) or server-side router (custom). - Offline: pre-downloaded routing graph for region.
Voice nav:
AVSpeechSynthesizerfor turn announcements.AVAudioSessioncategory.playback+.duckOthersto lower music.
Battery:
- Tone down GPS frequency when stopped at lights.
CLLocationManager.allowsBackgroundLocationUpdatesonly during active route.
Critical iOS-specific concerns
- Lock-screen widget: Live Activity for ongoing turn-by-turn.
- CarPlay: separate scene with simplified UI.
- Battery during navigation: aggressive — communicate to user with “high battery use” warning if route > 2hr.
- Privacy: location history stored locally, optional opt-in for “improve maps” upload.
Tradeoffs
- MapKit vs Mapbox vs Google Maps SDK: MapKit is free + integrated but less rich; Mapbox is most flexible but adds 10MB binary; Google has better global coverage.
- On-device vs server routing: on-device for offline + privacy; server for live traffic.
- CarPlay vs phone-only: CarPlay is App Store gold but adds a second UI to maintain.
At 10× scale
- Edge-cached routing per region.
- ML on-device for ETA refinement using personal driving history.
- Predictive offline downloads (auto-cache commute regions on Wi-Fi).
Scenario 5 — Video Streaming App (Netflix / Hulu)
Clarifying questions
- VOD only, or live? (VOD primarily; live for sports as stretch)
- DRM? (yes — FairPlay required for studio content)
- Offline downloads? (yes — for premium tier)
- AirPlay + Chromecast? (AirPlay yes; Chromecast nice-to-have)
High-level architecture
[iOS] ──> [Catalog API] ──> [Recommendation engine]
[iOS] ──HLS──> [CDN edge] ──> [Origin]
[iOS] ──> [License server (FairPlay)]
iOS architecture
Playback:
AVPlayer+AVPlayerLayer(UIKit) orVideoPlayer(SwiftUI iOS 14+).- HLS streaming with adaptive bitrate (server selects rendition by bandwidth).
AVPlayerItemlifecycle: prepare → play → end.
DRM:
AVAssetResourceLoaderDelegateto handle FairPlay key request.- Communicate with license server; cache license per asset for offline.
Offline downloads:
AVAssetDownloadTask(subclass ofURLSessionTask).- Background download via background
URLSession. - Manage storage limits; auto-evict oldest on space pressure.
Casting:
- AirPlay built into
AVPlayer(just enableAVRoutePickerView). - Chromecast via Google Cast SDK (extra dependency).
State:
- Watch progress synced to server (last position, last device).
- Resume across devices via “Continue Watching” rail.
Critical iOS-specific concerns
- Picture-in-Picture:
AVPlayerViewController.allowsPictureInPicturePlayback = true. - Background audio:
AVAudioSession.Category.playback+ Info.plistUIBackgroundModes: audio. - Now Playing info:
MPNowPlayingInfoCenterfor lock screen + AirPods controls. - Subtitles + closed captions:
AVMediaSelectionGroupfor language switching. - Bandwidth detection: HLS handles automatically; surface “low bandwidth” warning in UI.
- Video quality on cellular: respect
URLSessionConfiguration.allowsExpensiveNetworkAccessfor Low Data Mode.
Tradeoffs
- HLS vs DASH: HLS is iOS-native; DASH needs custom player. HLS wins on iOS.
- FairPlay vs Widevine: FairPlay required for iOS DRM; Widevine for Android. Most content goes through both.
- Pre-roll ads vs subscription: ad-supported tier needs
IMA SDKintegration. - Studio agreements: certain content geo-fenced; client must respect.
At 10× scale
- Predictive caching (download next episode of current binge on Wi-Fi).
- ML-based bitrate selection from device + network history.
- Live sports: low-latency HLS variant; sub-second glass-to-glass.
Common interview traps
- Jumping to code immediately. Spend 5 min clarifying; you’ll save 15 min mid-design.
- Designing the server. You’re being interviewed for iOS — keep server at “API contract” level unless asked.
- Ignoring offline. Every iOS design must address: app launched in airplane mode, what happens?
- Ignoring background. iOS aggressively suspends apps; designs that assume always-foreground are naive.
- Skipping privacy. ATT, Privacy Manifest, location justification — name them.
Lab preview
Lab 12.4 takes one of the scenarios above (Apple Notes sync) and walks you through producing a written design doc — the deliverable you’d send to a hiring manager for a take-home, or write up as a portfolio piece.