iOS interviews test the Swift type system, how ARC manages memory, and whether you can reason about UI state — plus at least one question where a retain cycle is hiding in plain sight. Below are the questions asked most often, each with a model answer. Middle: deeper understanding, optimization, and real-world situations.
1
What is the difference between a struct and a class in Swift? Which do you default to?
Answer
Structs are value types: assigning one copies it, they cannot inherit, and they are stack-allocated when possible. Classes are reference types with identity, inheritance, deinit, and ARC overhead. The Swift convention is to start with a struct and reach for a class only when you need reference semantics — shared mutable state, an Objective-C interoperability requirement, or a lifecycle you must observe.
2
How does ARC work, and what causes a retain cycle?
Answer
ARC tracks how many strong references point at an object and deallocates it when the count hits zero — it is compile-time inserted retain/release calls, not a runtime garbage collector. A retain cycle happens when two objects hold strong references to each other, so neither count ever reaches zero: a classic case is a view controller owning a closure that captures self strongly. You break it with [weak self] or [unowned self] in the capture list.
3
When do you use weak versus unowned?
Answer
Both avoid incrementing the retain count. weak makes the reference optional and sets it to nil when the object is deallocated, so it is safe if the other object may die first — delegates are the standard example. unowned is non-optional and assumes the referenced object outlives you; if that assumption breaks, the app crashes. Use unowned only when the lifetime relationship is genuinely guaranteed, and weak whenever you are unsure.
4
What is the difference between @State, @Binding, @StateObject and @ObservedObject?
Answer
@State is local value-type state owned by the view; SwiftUI stores it outside the struct so it survives re-renders. @Binding is a read-write reference to state owned by someone else, which is how a child mutates a parent's value. @StateObject creates and owns a reference-type observable object and is initialised exactly once. @ObservedObject observes an object owned elsewhere — using it where you needed @StateObject recreates the object on every re-render and silently resets its state.
5
How does SwiftUI decide to re-render a view?
Answer
A view is a lightweight struct describing the UI; SwiftUI rebuilds its body whenever a dependency it read changes, then diffs the result against the previous tree and applies only the differences to the underlying render. Dependencies are the property wrappers a view actually reads, plus the Environment. Performance problems almost always come from state placed too high in the tree, so an unrelated change invalidates a large subtree.
6
What does async/await change compared to completion handlers and GCD?
Answer
async/await lets asynchronous code read top to bottom, so error handling uses ordinary try/catch and there is no "pyramid of doom" or forgotten completion path. The compiler enforces that awaits happen in an async context, and structured concurrency ties child task lifetimes to their parent, so cancellation propagates. GCD is still relevant for legacy code and for fine-grained queue control, but new code should default to async/await, with Combine reserved for genuine streams of values over time.
7
What is an escaping closure and why does the keyword exist?
Answer
A closure is escaping when it is stored and called after the function returns — a network completion handler, for example. Swift requires the @escaping annotation because a non-escaping closure can be optimised more aggressively and cannot capture self strongly by accident. The annotation is also the signal to think about capture semantics: escaping closures are where retain cycles are born.
8
Walk through the UIViewController lifecycle. Where do you put layout code?
Answer
viewDidLoad runs once after the view hierarchy loads and is where you do one-time setup. viewWillAppear and viewDidAppear run on every presentation and are for work that must repeat, like refreshing data or starting an animation. Layout that depends on final frame sizes belongs in viewDidLayoutSubviews, because in viewDidLoad the bounds are not yet final — putting frame maths there is a very common bug that only shows on a different screen size.
9
How do you decide between Core Data, SwiftData, and simply writing files?
Answer
Core Data is worth its complexity when you have relational data, need queries, faulting, and change tracking across a large object graph. SwiftData is the modern Swift-native wrapper over the same stack and is the default for new apps on recent OS versions. For a handful of settings, use UserDefaults; for a cache of decoded models, plain Codable files or a lightweight key-value store are far simpler to reason about than a full persistent container.
10
How do you diagnose a slow app launch or a memory leak?
Answer
For launch time, use Instruments' App Launch template to see what runs before the first frame; the usual culprits are synchronous disk or network work in didFinishLaunching and heavy dependency graphs built eagerly. For leaks, the Memory Graph Debugger shows objects that should have been deallocated and who still points at them, which surfaces retain cycles directly. The Leaks instrument catches classic leaks, but the graph is better for cycles because it names the retaining reference.
🦎
Reading answers is not enough
In a real interview you speak under pressure. Cam asks these same questions, scores every answer, and shows exactly what to fix.
Practice a Middle iOS (Swift) interview →Free · 3 interviews per month