Quick answer
Strong iOS interview answers show how language fundamentals, architecture, and product quality fit together. SwiftUI familiarity helps, but interviewers are really testing judgment, state management, and mobile reliability.
Start here: iOS Developer Interview Prep
Read this next
- Frontend Engineer Interview Questions
- Web Performance Interview Questions
- Technical Interview Questions Hub
Introduction
iOS engineering interviews are a distinct discipline from general SWE interviews. Companies like Apple, Uber, Airbnb, Spotify, and top mobile startups test Swift language mastery, UIKit/SwiftUI architecture, memory management, and mobile-specific system design. This guide covers the questions that appear most frequently.
Swift Language Fundamentals
Q: How does Automatic Reference Counting (ARC) work in Swift?
ARC tracks the number of strong references to each class instance. When the count drops to zero, the instance is deallocated and memory is freed—automatically, without a garbage collector.
class Person {
var name: String
init(name: String) { self.name = name }
deinit { print("\(name) is being deallocated") }
}
var reference1: Person? = Person(name: "Alice") // reference count: 1
var reference2 = reference1 // reference count: 2
reference1 = nil // reference count: 1
reference2 = nil // reference count: 0 → deallocated
Q: What is a retain cycle and how do you break it?
A retain cycle occurs when two objects hold strong references to each other, preventing ARC from ever reaching zero for either.
class Dog {
var owner: Owner? // strong reference
}
class Owner {
var dog: Dog? // strong reference → cycle!
}
Solution: Use weak or unowned references.
weak var owner: Owner?— Optional; becomes nil when the referenced object is deallocated. Use when the referenced object's lifetime is shorter than or equal to the referencing object.unowned var owner: Owner— Non-optional; assumes the referenced object outlives the reference. Use when the referenced object always outlives the referencing object.
The most common retain cycle in practice is the closure-self cycle. Fix it with a capture list: [weak self].
Q: What is the difference between a struct and a class in Swift?
| Struct | Class | |
|---|---|---|
| Type | Value type | Reference type |
| Inheritance | No | Yes |
| Memory | Stack (usually) | Heap |
| Mutability | Copy on assignment | Shared reference |
| ARC | No | Yes |
When to prefer structs: Data models, lightweight containers, anything that benefits from value semantics (no unintended sharing). Swift standard library types (String, Array, Dictionary) are all structs.
When to prefer classes: Object identity matters, inheritance is needed, shared mutable state is required (use with caution).
Practice this with Interview Masters
Use Interview Masters to turn this iOS guide into repeatable Swift, architecture, and behavioral drills before your next mobile loop.
Concurrency
Q: How does Swift's async/await differ from completion handlers?
Completion handlers create "callback hell," are hard to compose, and lack compiler-enforced error handling. async/await enables linear, readable asynchronous code with full throws integration.
// ❌ Callback pyramid
fetchUser(id: 42) { user, error in
guard let user = user else { return }
fetchPosts(for: user) { posts, error in
guard let posts = posts else { return }
//...
}
}
// ✅ async/await
func loadUserContent() async throws {
let user = try await fetchUser(id: 42)
let posts = try await fetchPosts(for: user)
// linear, readable, error-propagating
}
Q: What is an Actor in Swift and what problem does it solve?
An actor is a reference type that protects its mutable state from concurrent access, eliminating data races without manual locking.
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
func getBalance() -> Double {
return balance
}
}
// Accessing actor state requires await (async context)
let account = BankAccount()
await account.deposit(100)
let balance = await account.getBalance()
@MainActor is a special global actor that ensures UI updates happen on the main thread—replacing DispatchQueue.main.async.
SwiftUI Architecture & State Management
Q: Explain the SwiftUI state management property wrappers.
@State— Local view state. Simple value types owned by the view. Causes view re-render on change.@Binding— A reference to state owned by a parent view. Two-way data binding.@ObservedObject— Observes an externalObservableObject. View re-renders when any@Publishedproperty changes.@StateObject— Like@ObservedObjectbut the view owns the object's lifecycle (created once, not on every re-render).@EnvironmentObject— Injects a sharedObservableObjectfrom the environment (dependency injection without passing through every view).
Q: What is the MVVM pattern in SwiftUI and why is it preferred?
MVVM (Model-View-ViewModel) separates business logic from the view layer.
- Model: Data structures and business logic.
- ViewModel: Prepares data for display, handles user actions, conforms to
ObservableObject, exposes@Publishedproperties. - View: Purely declarative SwiftUI code that renders based on ViewModel state.
This makes code testable (ViewModels have no UIKit/SwiftUI dependency), reusable, and maintainable. Avoid putting network calls or complex business logic directly in SwiftUI Views.
UIKit Questions (Still Commonly Asked)
Q: What is the UIViewController lifecycle and when does each method fire?
loadView()— Creates the view hierarchy. Override only when building views in code (no XIB/Storyboard).viewDidLoad()— Called once after the view is loaded into memory. Best place for one-time setup.viewWillAppear(_:)— Called every time the view is about to become visible. Use for refreshing data.viewDidAppear(_:)— View is now on screen. Use for starting animations or analytics.viewWillDisappear(_:)— View is about to leave the screen.viewDidDisappear(_:)— View has left the screen. Stop timers, pause media.
Q: How does Auto Layout work and what causes constraint conflicts?
Auto Layout uses a constraint-based system to calculate view frames at runtime. The engine solves a system of linear equations derived from your constraints.
Conflicts occur when constraints are over-specified (conflicting priorities) or under-specified (ambiguous layout). Use UIView.constraintDebugDescription and the Debug View Hierarchy tool to diagnose conflicts. setContentCompressionResistancePriority and setContentHuggingPriority control how views resize under pressure.
Mobile System Design
Q: How would you design the architecture for an offline-capable iOS app?
Key components:
- Local persistence: CoreData or GRDB for structured data, FileManager for blobs.
- Sync engine: Tracks dirty records (modified while offline), queues operations, syncs when connectivity is restored.
- Conflict resolution strategy: Last-write-wins, server-wins, or CRDTs depending on the use case.
- Connectivity monitoring:
NWPathMonitor(Network framework) to detect connectivity changes and trigger sync. - User feedback: Clear UI indicators for sync state (synced, pending, failed).
Summary
iOS interviews reward engineers who understand Swift deeply—not just the syntax, but the memory model, concurrency primitives, and architectural patterns that make iOS apps performant and maintainable. Practice explaining ARC, actor isolation, and SwiftUI state management clearly and concisely.
