Common Mistakes
How to read this chapter
Each mistake has a weak answer and a better answer. The weak answers are real patterns interviewers see every week. Most of them are not "wrong". They are just missing the reason, the trade-off or the mobile angle.
1. Designing before asking
Weak: "OK, a chat app. So we have a ChatViewController, and it calls the API..."
Better: "Before I design, a few questions. Is it one-to-one or group chat? Must it work offline? How fresh must messages be? Let me write down the scope."
Without requirements, you may design the wrong app for 40 minutes. Spend the first 5 minutes on scope.
2. Designing the backend
Weak: "We put a load balancer in front of the chat servers, shard the messages by user ID, and use Kafka for fan-out..."
Better: "I'll treat the backend as a black box with this API. The interesting part for us is the client: the local database, the send queue and the WebSocket."
The round is about the app. Talk about the server only where it touches the app: endpoints, data format, push and pagination.
3. Buzzwords without reasons
Weak: "I'd use VIPER, Combine, Core Data and Realm."
Better: "I'd use MVVM, because it fits SwiftUI and keeps logic testable. For storage I need queries over thousands of messages, so I'd use SQLite or Core Data. The trade-off is setup cost compared to plain JSON files."
Every tool you name needs a "because". If you cannot say why, do not name it.
4. Only the happy path
Weak: "The view model loads the posts and the list shows them."
Better: "The screen has five states: loading, loaded, empty, error, and offline with saved data."
enum ScreenState<Value> {
case loading
case loaded(Value)
case empty
case failed(message: String, canRetry: Bool)
case offline(saved: Value)
}Real apps spend most of their code on the unhappy paths. Show that you know it.
5. Ignoring the network
Weak: "When the user taps Send, we call POST /messages."
Better: "When the user taps Send, the message is saved locally with a 'sending' state and shown at once. An outbox sends it when we are online, with an idempotency key so retries are safe."
Phones lose the network in elevators, trains and basements. A mobile design that assumes a perfect network is a weak design.
6. One giant view model
Weak: "The FeedViewModel calls URLSession, parses JSON, saves to Core Data and formats the
dates."
Better: "The view model only holds screen state. A repository decides between disk and network. An HTTP client does the requests. Each one is behind a protocol."
// Weak: the view model does everything, so nothing can be tested alone
final class FeedViewModelWeak {
func load() async throws -> [Post] {
let (data, _) = try await URLSession.shared.data(from: feedURL)
return try JSONDecoder().decode([Post].self, from: data)
}
}
// Better: one job, one injected dependency
@MainActor
final class FeedViewModelBetter {
private(set) var posts: [Post] = []
private let repository: FeedRepository
init(repository: FeedRepository) {
self.repository = repository
}
func load() async throws {
posts = try await repository.fetchFeed(cursor: nil).posts
}
}7. Singletons everywhere
Weak: "Any screen can call NetworkManager.shared or Database.shared."
Better: "Dependencies are passed in through init from one composition root. Tests pass fakes.
There is no hidden global state."
Singletons hide dependencies and make tests hard. It is fine to have one instance of something. It is not fine for everyone to reach it globally.
8. No trade-offs
Weak: "I'll use a WebSocket."
Better: "I'll use a WebSocket while the app is open, because messages must arrive in about a second. The cost is battery use and reconnect logic. When the app is in the background, I'll close it and rely on push notifications."
Use this pattern: choice, reason, cost, mitigation. Mitigation means how you reduce the cost.
9. Over-engineering
Weak: "For the to-do app, I'll use CRDTs, 15 Swift packages and a custom DI framework."
Better: "For a to-do app with one user and two devices, last-write-wins per item is enough. I'll keep one app target with folders. If the team grows, I'd split it into modules."
Senior engineers pick the simplest design that meets the requirements, and say how it can grow later.
10. Forgetting the main thread
Weak: "The data comes back and we update the table view."
Better: "Networking and parsing run off the main thread. The view model is @MainActor, so every
UI update happens on the main thread. Swift 6 checks this at compile time."
@MainActor
@Observable
final class ProfileModel {
private(set) var name = ""
private let api: ProfileAPI
init(api: ProfileAPI) {
self.api = api
}
func load() async {
// The request runs off the main actor. We come back here to set `name`.
name = (try? await api.profile().name) ?? "Unknown"
}
}11. Forgetting memory
Weak: "We download the images and show them in the cells."
Better: "Images are downsampled to the cell size. A 1080 x 1080 image is about 4.7 MB in memory, but a 130-point cell only needs about 0.6 MB. The memory cache has a cost limit, and there is a disk cache for repeat visits."
A number like this makes your point concrete. Interviewers remember it.
12. Breaking old app versions
Weak: "We rename user_name to display_name in the API."
Better: "Old app versions stay installed for months. So the API only adds fields. The app ignores
unknown fields and maps unknown enum values to a fallback. For a real break, we bump to /v2 or use
a forced update."
enum PostKind: String, Decodable {
case photo, video, unknown
init(from decoder: any Decoder) throws {
let raw = try decoder.singleValueContainer().decode(String.self)
self = PostKind(rawValue: raw) ?? .unknown // a new kind from the server does not crash old apps
}
}13. Skipping privacy and accessibility
Weak: "We save the auth token in UserDefaults."
Better: "The token goes in the Keychain. Personal files use data protection. Logs never contain personal data. All controls have VoiceOver labels, and text uses Dynamic Type."
These take one minute to say and show maturity.
14. Bad time management
Weak: 20 minutes on requirements, then a rushed diagram, and no deep dives.
Better: Follow time boxes: about 5 minutes for requirements, 8 for the diagram, 10 for data and API, 17 for deep dives, and 5 for the wrap-up. If you are late, say: "In the interest of time, I'll skip X and go to Y."
15. Going silent
Weak: Two minutes of silent drawing.
Better: Think out loud: "I'm deciding between a database and JSON files. The list can have thousands of items and we need sorting, so I'm leaning to a database."
The interviewer can only score what they hear. Silence also stops them from giving you hints.
16. Never checking in
Weak: A 30-minute monologue.
Better: "Does this level of detail work for you?" or "Should I go deeper on sync or move on to images?"
The interviewer often has a topic in mind. Checking in helps you find it.
A final checklist
Before you say "that's my design", check that you covered:
- Functional and non-functional requirements, and what is out of scope
- A diagram with layers and a single source of truth
- Swift types for the data model
- Endpoints, pagination and the key protocols
- Loading, empty, error and offline states
- 2 or 3 deep dives with trade-offs
- Main thread, memory and battery
- Privacy and accessibility
- Testing, metrics and rollout
- A 30-second summary at the end
You will not cover all of it deeply. That is fine. Mentioning each one briefly still shows you know it matters.