iOS · 16 min
Senior iOS interview questions for 2026, and the follow-ups
Twelve senior iOS interview questions for 2026: what each scores, a weak and a senior answer, and the follow-up on Swift 6 concurrency, SwiftUI, architecture.
iOS · 13 min read
A complete iOS mock interview you can run alone or with a partner: timing, eight prompts with follow-ups, a scoring rubric and how to self-score a recording.
Reading answers is not practice. An iOS mock interview is practice only when a clock is running, someone (or a recording) is watching, and the second question arrives before you feel ready for it. This page gives you a complete round you can run tonight: the format, eight prompts across five rounds, the follow-up for each, a rubric, and a way to score yourself honestly from a recording.
I have sat on the interviewer side of more than 500 technical interviews. The candidates who improved fastest were not the ones who read the most. They were the ones who ran a timed round, watched the tape, and fixed one thing before the next attempt.
The whole mock takes about 90 minutes, split into rounds that mirror a typical iOS loop; real companies differ in which rounds they run and how long each lasts. You can run all of it in one sitting or one round per evening.
| Round | Time | What it tests |
|---|---|---|
| 1. Swift fundamentals | 10 min | Precision, and whether you correct your own overclaims |
| 2. SwiftUI and state | 10 min | Ownership, identity, measuring before fixing |
| 3. Concurrency | 15 min | Isolation, reentrancy, cancellation |
| 4. Live coding | 30 min | Clarify, build, test, narrate |
| 5. Short design prompt | 15 min | Scope, data flow, failure cases |
| 6. Behavioural | 10 min | Specific story, your role, a number |
Two rules make it a mock and not a quiz. First, no notes and no documentation during a round; if you would look something up in a real interview, say so out loud and move on. Second, every prompt gets its follow-up, even when your first answer felt good. The follow-up is where the score is decided, so skipping it measures nothing.
Alone: put the prompts in a text file, hide the follow-ups below a long gap, and start a screen recording with your camera and microphone on. Read the prompt, answer out loud, then scroll to the follow-up and answer that too. Do not pause the recording to think; silence is part of what you are measuring.
With a partner: give them this page and ask them to read only the prompt and follow-up, never the notes on what a strong answer contains, until the round is over. Ask them to interrupt you at least once per round with a clarifying question or a mild challenge. A partner who never pushes gives you a comfortable round and a useless score.
Either way, score only after the whole round ends. Scoring between prompts turns a mock into a study session, and the point is to feel the pressure of the next question arriving while the last one still stings.
What is scored: whether you are precise about what value semantics guarantee.
Weak answer: “Structs are thread-safe, so I use them by default.”
Strong answer: “I default to structs because copies are independent and easy to reason about. The guarantee only goes as deep as the stored properties: if a struct holds a class instance, two copies share that instance. I use a class when I need shared identity, a deinit, or reference semantics on purpose.”
The follow-up: “Show me a case where a struct copy surprised someone.” Have a concrete example ready: a struct holding a reference-type configuration, copied into a closure, then mutated through the shared reference.
some and any?”What is scored: whether you can name the cost, not only the syntax.
Strong answer: “some hides a single concrete type that the compiler still knows, which keeps static dispatch and specialization. any is an existential box that can hold any conforming type, at the price of dynamic dispatch, possible allocation, and less room for the optimiser. I reach for any when I really need a heterogeneous collection.”
The follow-up: “Why can’t I compare two elements of [any Equatable] with ==?” Because == needs both sides to be the same concrete type, and the existential has erased that information.
The pattern in this round is small overclaims. Interviewers repeat your answer back slightly too strong to see whether you notice. If you hear yourself say “always” or “thread-safe”, stop and narrow it before they do. That correction, made calmly and without being asked, is worth more than the original sentence.
@State, @StateObject or @ObservedObject?”What is scored: ownership and lifetime, the real question behind the three wrappers.
Strong answer: “The view that creates the model owns it. With an @Observable model on iOS 17 and later, that is @State; with an ObservableObject, it is @StateObject. A view that receives a model from its parent does not own it. The bug I see most is a model created inside a view marked @ObservedObject, which gets recreated whenever the parent’s body runs.”
The follow-up: “The state resets when the user toggles a filter. Why?” Look for identity: an if branch or a changing id makes SwiftUI treat the view as new and throw its state away.
What is scored: whether you measure before you change code.
Strong answer: “Reproduce it on a real device in a Release build, then profile with Instruments before touching anything. Inside body during development, Self._printChanges() tells me which dependency triggered a re-evaluation. Common causes are work inside body, unstable identity in ForEach, and image decoding on the main thread.”
The follow-up: “You fixed it. How do you know?” Name the measurement you took before, the one after, and the device you took both on. “It feels smoother” is not an answer an interviewer can write down.
What is scored: actor reentrancy, explained with a failure rather than a definition.
Strong answer: “Both can miss the cache and both start a download, because the actor is released at the await and the second call runs before the first resumes. I store the in-flight Task in a dictionary so the second caller awaits the first caller’s task instead of starting its own.”
The follow-up: “The screen that asked first goes away. Should the download be cancelled?” There is no single answer. For a shared cache you usually let it finish because another caller still wants the result; say that trade-off out loud.
@MainActor on a view model guarantee?”Strong answer: “Its synchronous code runs on the main actor, and the compiler stops other isolation domains from touching its state directly. It does not mean the work it awaits runs on the main thread: during a network await, the main actor is free. Heavy synchronous work inside the type, like decoding a large payload, still blocks it.”
The follow-up: “Where would you move the decode?” Into a nonisolated function or a separate type, with a Sendable result crossing back to the main actor.
If round 3 goes badly, it usually goes badly in the same way: the first sentence is correct and the second sentence is missing. Rehearse the second sentence. The definition gets you to the follow-up; the failure case and the fix get you through it.
Give yourself 30 minutes in Xcode or a plain editor. Spend the first five minutes clarifying out loud: should an in-flight search be cancelled when the query changes, what happens on an empty query, where the results go. Then build the smallest version that works, and test it before you call it done.
@MainActor
@Observable
final class SearchModel {
var query = "" { didSet { scheduleSearch() } }
private(set) var results: [String] = []
private var searchTask: Task<Void, Never>?
private let search: @Sendable (String) async throws -> [String]
private let debounce: Duration = .milliseconds(300)
init(search: @escaping @Sendable (String) async throws -> [String]) {
self.search = search
}
private func scheduleSearch() {
searchTask?.cancel()
let term = query
guard !term.isEmpty else { results = []; return }
searchTask = Task { [debounce] in
do {
try await Task.sleep(for: debounce)
results = try await search(term)
} catch {
// Cancelled by a newer keystroke, or the search failed.
}
}
}
}The follow-up: “How would you test the debounce without waiting 300 ms in every test?” A good answer injects the delay or a clock, and names what the test asserts: one search call for a burst of keystrokes, and no stale results after a newer query. A second follow-up worth practising: “Your catch block swallows real search errors. What would you change?”
Notice what the interviewer watches in this round. It is less the code than the order: clarify, build the core, test it, then polish. A candidate who spends ten minutes on a perfect Debouncer abstraction and never runs the core path scores lower than one with plain code that works and a test that proves it.
What is scored: scope, data flow and the one hard case.
Strong answer, in the first three minutes: the local store is the source of truth for the UI. Writes land locally first and join an outbox. A sync process drains the outbox when the network comes back. Then state your conflict rule before you are asked for it.
The follow-up: “The same note is edited on two devices while both are offline.” Last write wins is acceptable for a settings toggle and destructive for note text. Say what you would do instead, such as merging per field or keeping both versions for the user to resolve.
The full treatment of this round is in iOS system design interview. For the mock, fifteen minutes is enough to find out whether you lead with the data flow or wander into UI details first.
Pick one story and tell it in two minutes: “Tell me about a technical decision you pushed for that others disagreed with.” The follow-ups, in order: “What was your role, not the team’s?”, “What did it cost?”, “What number changed afterwards?”, “What would you do differently?”
A strong story is specific, names your part in it, shows you worked with people rather than around them, and ends with a result and an honest note about what is still unfinished. If your story has no number in it, find one before the real loop. Most stories that feel vague are missing exactly that.
Score each round from 1 to 5 on four columns. The columns are the same ones I use when I write up a real candidate.
| Column | 1 | 3 | 5 |
|---|---|---|---|
| Signal | A textbook definition, no production detail | Correct, with one real example | Says something only someone who shipped it would say |
| Structure | Jumps between ideas, none finished | Mostly in order, one detour | Easy to follow without interrupting |
| Depth | Collapses at the first follow-up | Holds one follow-up | Holds the second and third push |
| Judgement | Same answer when the constraint changes | Adjusts when prompted | Names the constraint that would change the answer before being asked |
Six rounds, four columns, maximum 120. Do not treat the total as a pass mark; there is no number that predicts an offer. Look instead at the lowest column across rounds. That column is what you practise this week.
Watch the recording the next day, not the same evening. Fresh eyes are harsher and more accurate. Then work through it with a simple sheet:
Then run the same round again within a few days with that single correction in place. Two runs of one round with a real fix between them teach more than six new rounds with no review.
@MainActor written on every type.@concurrent attribute marks a function that should run concurrently, which makes it visible in the code when work leaves the actor.Run the round once this week, watch the recording, and write down one correction. That single loop, repeated, is what moves a mock score and, more to the point, a real one.

iOS · 16 min
Twelve senior iOS interview questions for 2026: what each scores, a weak and a senior answer, and the follow-up on Swift 6 concurrency, SwiftUI, architecture.

iOS · 12 min
What an iOS coding interview asks: the practical tasks that recur, the algorithm round in Swift, and what is graded beyond whether it compiles.

iOS · 13 min
SwiftUI interview questions on identity, state ownership, Observation and performance, each with the answer that scores, the trap and the follow-up.