System design interviews
In a mobile system design round you plan an app or a feature at a high level. There is no "correct" design. The interviewer wants to see how you think, how you structure the problem, and how you choose between options.
This chapter is a short overview. The separate iOS System Design Playbook covers each step in depth, with full worked designs.
What the round looks like
The interviewer gives a short, open prompt:
- "Design a photo feed app, like Instagram."
- "Design a chat app."
- "Design offline support for a notes app."
- "Design an image loading library."
You get 45–60 minutes. You draw boxes on a whiteboard or an online drawing tool, and you talk.
It is not a backend design round. You do not need to design databases with sharding. You focus on the app: its layers, data, networking, caching, offline and performance. You still talk about the API between the app and the server.
What interviewers look for
- You ask questions first. You do not start drawing after 10 seconds.
- You have a clear structure. The interviewer can follow you.
- You go deep where it matters. Not 2 minutes on everything; 10 minutes on the hard parts.
- You talk about trade-offs. "Option A is simpler, option B works offline. I pick B because…"
- You know mobile limits. Bad networks, battery, memory, app killed in the background, small screens.
The framework
Use these 6 steps. Write them in the corner of the board so you do not forget one.
| Step | Time (45 min) | What you do |
|---|---|---|
| 1. Requirements | 5–7 min | Ask questions. Agree on scope. |
| 2. High-level architecture | 8–10 min | Draw the main boxes and arrows. |
| 3. Data model | 5 min | Show the main Swift types. |
| 4. API | 5 min | List the server endpoints and the key protocols. |
| 5. Deep dives | 15 min | Go deep on 2–3 hard parts. |
| 6. Trade-offs and wrap-up | 3–5 min | Summarise choices and what you would do next. |
Let us walk through it with one example: "Design a photo feed app."
Step 1: Requirements
Split them into three groups.
Functional (what it does):
- Users see a feed of posts (photo, caption, author, likes).
- The feed scrolls forever (pagination).
- Users can like a post.
Non-functional (how well it does it):
- Smooth scrolling (no dropped frames).
- Works on slow networks.
- Shows the last feed when offline.
Out of scope (say it out loud):
- Uploading photos, comments, direct messages.
Good questions to ask:
- "How many posts per page? Are images large?"
- "Must the feed work offline, or just show cached data?"
- "Should likes update in real time from other users?"
- "Which iOS versions do we support?"
Step 2: High-level architecture
Draw the layers. Keep the arrows going one way.
+-------------------+ +----------------+
| FeedView +------->+ ImageLoader | memory + disk cache
+---------+---------+ +----------------+
|
+---------v---------+
| FeedViewModel | screen state, user actions
+---------+---------+
|
+---------v---------+
| FeedRepository | single source of data
+-----+-------+-----+
| |
+-----v---+ +-v------------+
|APIClient| | FeedStore | local database (offline)
+---------+ +--------------+Say what each part does in one sentence:
- FeedView: shows posts, sends "load more" and "like" actions.
- FeedViewModel: holds the screen state, calls the repository.
- FeedRepository: the single source of data. It decides: cache, network, or both.
- APIClient: HTTP requests and decoding.
- FeedStore: saves posts on the device for offline use.
- ImageLoader: downloads and caches images. The cells use it directly, so posts and images load separately.
Step 3: Data model
Show the main types in Swift. Keep them small.
struct Post: Identifiable, Codable, Sendable {
let id: String
let author: Author
let imageURL: URL
let caption: String
var likeCount: Int
var isLikedByMe: Bool
let createdAt: Date
}
struct Author: Codable, Sendable {
let id: String
let name: String
let avatarURL: URL
}
struct FeedPage: Codable, Sendable {
let posts: [Post]
let nextCursor: String? // nil means no more pages
}Step 4: API
List the endpoints and the key protocols.
GET /feed?cursor=abc&limit=20 -> FeedPage
POST /posts/{id}/like
DELETE /posts/{id}/likeprotocol FeedRepository: Sendable {
func firstPage() async throws -> FeedPage
func nextPage(after cursor: String) async throws -> FeedPage
func setLiked(_ liked: Bool, postID: String) async throws
}Mention why you use a cursor and not a page number: new posts at the top do not cause duplicates or skipped items.
Step 5: Deep dives
Pick 2–3 topics. Choose the ones that matter most for this app. Often the interviewer will pick one for you.
For a photo feed, good choices:
Image loading and caching
- Two-level cache: memory (fast, small) and disk (slower, bigger).
- Download images at the size you show, not the full size.
- Cancel downloads for cells that scroll off screen.
- Do not download the same URL twice at the same time.
actor ImageLoader {
private var inFlight: [URL: Task<Data, Error>] = [:]
func data(for url: URL) async throws -> Data {
if let task = inFlight[url] {
return try await task.value // join the running download
}
let task = Task {
try await URLSession.shared.data(from: url).0
}
inFlight[url] = task
defer { inFlight[url] = nil }
return try await task.value
}
}Pagination
- Load the next page when the user is 5 items from the end.
- Keep a flag so you never load the same page twice.
- Stop when
nextCursorisnil.
Offline
- Save the first page to the local store.
- On launch, show cached posts first, then refresh from the network.
- Show a small "You are offline" banner instead of an error screen.
Optimistic likes
- Update the UI at once when the user taps "like".
- Send the request in the background.
- If it fails, undo the change and show a short message.
Step 6: Trade-offs and wrap-up
End with a small table and one or two next steps.
| Choice | Pros | Cons |
|---|---|---|
| Cursor pagination | No duplicates when new posts arrive | Cannot jump to page 10 |
| Optimistic likes | Feels instant | Must undo on failure |
| SwiftData for offline | Less code, works well with SwiftUI | Needs a newer iOS version; less control than raw SQLite |
| Own image loader | No dependency, full control | More code to maintain than a library |
"If I had more time, I would add prefetching for images, and metrics for scroll performance and image load time."
Common mistakes
- Starting to draw before asking questions. You may design the wrong thing.
- Designing the backend. Keep the focus on the app.
- Staying too shallow. Boxes and arrows are not enough. Go deep on at least two parts.
- No trade-offs. Every choice has a cost. Say it.
- Ignoring mobile problems. Bad network, app killed in the background, low memory, battery.
- Not listening to hints. If the interviewer asks "What about offline?", it is a strong signal to go there.
Useful phrases
- "Before I start, can I ask a few questions about scope?"
- "I will keep uploads out of scope, unless you want me to include them."
- "There are two options here. Let me compare them."
- "Which part would you like me to go deeper on?"
- "To summarise: the repository is the single source of truth, with a disk cache for offline."
Topics to know for this round
- Layers: view, view model, repository, network, storage
- Caching: memory, disk, HTTP cache, cache invalidation
- Pagination: cursor vs offset
- Offline: local storage, sync, conflict handling
- Real-time: WebSockets, polling, push notifications
- Concurrency: actors, cancellation, avoiding duplicate requests
- Memory and performance: image sizes, cell reuse, main thread work
- Errors and retries: backoff, user messages
- Security: tokens in Keychain, HTTPS
- Modularisation: feature modules with Swift packages
How to practise
- Use the system design questions on this site. Try each one alone for 45 minutes, out loud, before you read the full design.
- Draw on paper or in a simple drawing app. Practise drawing fast.
- Read the iOS System Design Playbook for the full framework, more deep dives and complete example designs.
- Practise with a friend. Ask them to interrupt you with "What about offline?" or "What if the image is 20 MB?"