The 45-Minute Framework
The plan at a glance
Use the same six steps for every prompt. Then you never wonder "what now?".
| Step | Time | What you produce |
|---|---|---|
| 1. Requirements | 5 min | A short list: features, non-functional needs, out of scope |
| 2. High-level architecture | 8 min | A box-and-arrow diagram |
| 3. Data model | 5 min | 2 to 4 Swift types |
| 4. API and interfaces | 5 min | Server endpoints and the key Swift protocols |
| 5. Deep dives | 17 min | 2 or 3 topics in depth |
| 6. Wrap-up | 5 min | Trade-offs, risks, what you would do next |
Is the round 60 minutes? Give the extra time to the deep dives.
Say the plan out loud at the start:
"I'd like to start with requirements, then draw the main parts, then the data model and API. After that I'd go deep on two or three areas. Does that work for you?"
This shows structure. It also lets the interviewer steer you early.
This chapter uses one example the whole way: "Design a photo feed app."
Step 1: Requirements (5 minutes)
Goal: agree on what you will build. Ask a few questions, then write the answers down.
Functional
- Scroll a feed of posts (image, author, caption, like count)
- Like and unlike a post
- Pull to refresh, load more at the bottom
Non-functional
- Opens fast, shows saved posts when offline
- Smooth scrolling on older iPhones
- Works on slow networks
- VoiceOver and Dynamic Type support
Out of scope
- Uploading photos, comments, storiesKeep 3 or 4 core features. Move the rest to "out of scope". You cannot design everything in 45 minutes, and the interviewer knows it. The next chapter covers which questions to ask.
Step 2: High-level architecture (8 minutes)
Draw the main parts. Start at the screen and go down to the network.
+-------------+ +----------------+ +------------------+
| FeedView | --> | FeedViewModel | --> | FeedRepository |
+-------------+ +----------------+ +------------------+
| |
v v
+-----------+ +-----------+
| FeedAPI | | FeedStore |
| (network) | | (disk) |
+-----------+ +-----------+
|
v
[ Server ]
ImageLoader (memory + disk cache) is shared by all feed cells.Give each box one sentence:
- FeedView: shows posts, sends taps to the view model.
- FeedViewModel: holds screen state (posts, loading, error).
- FeedRepository: the single source of truth for posts. It reads the disk and the network.
- FeedAPI: talks to the server.
- FeedStore: saves posts on disk for offline use.
Then explain the data flow in two sentences: "The view model asks the repository for posts. The repository returns saved posts at once, then fresh posts from the API."
Step 3: Data model (5 minutes)
Write the core types. Keep them small. Add only fields the features need.
import Foundation
struct User: Hashable, Codable, Sendable {
let id: String
let name: String
let avatarURL: URL?
}
struct Post: Identifiable, Hashable, Codable, Sendable {
let id: String
let author: User
let imageURL: URL
let caption: String
var likeCount: Int
var isLiked: Bool
let createdAt: Date
}
struct FeedPage: Sendable {
let posts: [Post]
let nextCursor: String? // nil means "no more pages"
}Say a few words about each choice. "IDs are strings from the server. likeCount and isLiked
are var because the user can change them. Codable lets us save posts to disk."
Step 4: API and interfaces (5 minutes)
Two parts: the server endpoints and the Swift protocols.
GET /v1/feed?cursor={cursor}&limit=20 -> 200 { "posts": [...], "next_cursor": "..." }
PUT /v1/posts/{id}/like -> 204
DELETE /v1/posts/{id}/like -> 204protocol FeedRepository: Sendable {
func cachedFeed() async -> [Post]
func fetchFeed(cursor: String?) async throws -> FeedPage
func setLiked(_ liked: Bool, postID: String) async throws
}Why PUT and DELETE and not one "toggle" call? They are idempotent. Idempotent means you can
send the same request twice and get the same result. So a retry is always safe.
Step 5: Deep dives (17 minutes)
This is where you show depth. Pick 2 or 3 topics that matter for this product. Then ask:
"I'd like to go deeper on image loading and the offline cache. Is there an area you prefer?"
Good deep dives by product:
| Product | Good deep dives |
|---|---|
| Photo feed | Image loading, pagination, prefetching, offline cache |
| Chat | Real-time updates, offline send queue, message order |
| Notes | Offline sync, conflict resolution |
| Ride tracking | Location updates, battery, background modes |
| Video | Streaming, memory, downloads for offline |
Use the same shape for each topic: problem, options, choice, trade-off.
Example: "Liking a post should feel instant." The usual answer is an optimistic update. You change the UI first, call the server, and roll back if the call fails.
import Observation
@MainActor
@Observable
final class FeedViewModel {
private(set) var posts: [Post] = []
var errorMessage: String?
private let repository: FeedRepository
init(repository: FeedRepository) {
self.repository = repository
}
func toggleLike(postID: String) async {
guard let post = posts.first(where: { $0.id == postID }) else { return }
let liked = !post.isLiked
apply(liked: liked, to: postID) // 1. update the UI now
do {
try await repository.setLiked(liked, postID: postID) // 2. tell the server
} catch {
apply(liked: !liked, to: postID) // 3. roll back on failure
errorMessage = "Could not update the like. Try again."
}
}
private func apply(liked: Bool, to postID: String) {
// Look up the index again: the array can change while we wait for the server.
guard let index = posts.firstIndex(where: { $0.id == postID }),
posts[index].isLiked != liked else { return }
posts[index].isLiked = liked
posts[index].likeCount += liked ? 1 : -1
}
}Point out the small detail in apply. After an await, the list may have changed, for example
after a refresh. Never keep an old index across an await. Interviewers love this detail.
Step 6: Wrap-up (5 minutes)
Summarize in 30 seconds. Then name trade-offs and next steps.
"To sum up: MVVM with a repository that serves cached posts first, then fresh ones. Cursor pagination. Images are downsampled and cached in memory and on disk. Likes are optimistic. The main trade-off is that offline users may see old like counts. With more time I'd add metrics for scroll hitches and image load time, and ship the new feed behind a feature flag."
Good topics for the wrap-up:
- the 2 or 3 biggest trade-offs
- what can go wrong (risks)
- how you would test it
- how you would measure success (metrics)
- how you would roll it out
Managing time
- Check the clock. By minute 15 the diagram should be done. By minute 25 the API should be done.
- Follow the interviewer. If they dig into something early, go with it. Their interest is a signal.
- Never go silent. If you need to think, say "Let me think about this for a moment."
- Running late? Say it: "I'll skip the details of X so we have time for Y."
Phrases that help
- "Let me write down the requirements so we agree on the scope."
- "I'll start with the simple version and then improve it."
- "There are two options. A gives us X, B gives us Y. For this app I pick A, because..."
- "This is the part I'd test most carefully."
- "Is this the level of detail you want, or should I go deeper?"
Practice tip
Take one prompt and run the six steps with a timer. Record yourself. Listen for long silences and for places where you said "it depends" without saying on what. Fix those first.