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:

  1. Clarify scope (5 min): users, scale, platforms, must-have vs nice-to-have.
  2. High-level architecture (10 min): client + server boundary, data shape, caching strategy.
  3. Deep dive on iOS (20 min): screens, data flow, offline behavior, edge cases.
  4. Tradeoffs + scale (10 min): what breaks at 10×, what you’d add at 100×.
  5. 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 Post model (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:

  • LazyVStack inside ScrollView, with ForEach(posts, id: \.id).
  • Each post row is Equatable to skip redundant re-renders.
  • Prefetch +5 ahead on scroll via .task(id: post.id) per visible row.

State:

  • FeedViewModel (@Observable) with state: 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 likeCount locally, fire await 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, prepareForReuse cancels image load.
  • Background refresh: BGAppRefreshTask prefetches new posts when system allows.
  • Push notifications: silent push triggers feed-refresh-available badge.

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:

  • URLSessionWebSocketTask for live messages; auto-reconnect with exponential backoff.
  • Background URLSession for large media uploads.
  • HTTP for history fetch (paginated by time cursor).

State:

  • ChatViewModel per conversation; subscribes to AsyncStream from a central ConnectionManager.
  • 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 with AVAudioPlayer + 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 BGProcessingTask overnight.

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 deletedAt for tombstone-based sync.
  • Full-text search via SQLite FTS5 (@Model with 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.
  • SyncStatus observable showing “Synced” / “Syncing” / “Offline”.

Critical iOS-specific concerns

  • Editor performance: debounced auto-save (every 2 s of inactivity).
  • Background sync: BGAppRefreshTask on app suspend; trigger immediately on foreground.
  • Spotlight: index notes via CSSearchableItem for 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:

  • MapKit Map { } SwiftUI view (iOS 17+) with MapPolyline for route.
  • Custom tile overlay for offline regions (cached as MKTileOverlay).

Location:

  • CLLocationManager with kCLLocationAccuracyBestForNavigation during 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:

  • AVSpeechSynthesizer for turn announcements.
  • AVAudioSession category .playback + .duckOthers to lower music.

Battery:

  • Tone down GPS frequency when stopped at lights.
  • CLLocationManager.allowsBackgroundLocationUpdates only 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) or VideoPlayer (SwiftUI iOS 14+).
  • HLS streaming with adaptive bitrate (server selects rendition by bandwidth).
  • AVPlayerItem lifecycle: prepare → play → end.

DRM:

  • AVAssetResourceLoaderDelegate to handle FairPlay key request.
  • Communicate with license server; cache license per asset for offline.

Offline downloads:

  • AVAssetDownloadTask (subclass of URLSessionTask).
  • Background download via background URLSession.
  • Manage storage limits; auto-evict oldest on space pressure.

Casting:

  • AirPlay built into AVPlayer (just enable AVRoutePickerView).
  • 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.plist UIBackgroundModes: audio.
  • Now Playing info: MPNowPlayingInfoCenter for lock screen + AirPods controls.
  • Subtitles + closed captions: AVMediaSelectionGroup for language switching.
  • Bandwidth detection: HLS handles automatically; surface “low bandwidth” warning in UI.
  • Video quality on cellular: respect URLSessionConfiguration.allowsExpensiveNetworkAccess for 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 SDK integration.
  • 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

  1. Jumping to code immediately. Spend 5 min clarifying; you’ll save 15 min mid-design.
  2. Designing the server. You’re being interviewed for iOS — keep server at “API contract” level unless asked.
  3. Ignoring offline. Every iOS design must address: app launched in airplane mode, what happens?
  4. Ignoring background. iOS aggressively suspends apps; designs that assume always-foreground are naive.
  5. 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.


Next: 12.10 — Live Coding Playbook