iOS · 13 min read

iOS mock interview: a full round you can run in 2026

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 format and timing

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.

How to run it alone or with a partner

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.

Round 1: Swift fundamentals

1. “When do you choose a struct over a class?”

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.

2. “What is the difference between 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.

Round 2: SwiftUI and state

3. “Who owns this model: @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.

4. “This list stutters while scrolling. What do you do first?”

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.

Round 3: Concurrency

5. “Two callers ask an actor-based cache for the same image at once. What happens?”

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.

6. “What does @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.

Round 4: Live coding task

7. “Build a debounced search: typing updates a query, and a search runs 300 ms after the user stops typing.”

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.

Round 5: Short design prompt

8. “Design offline support for a notes app. You have 15 minutes.”

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.

Round 6: Behavioural

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.

The scoring rubric

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.

How to self-score a recording

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:

  1. Mark every silence longer than ten seconds. For each one, write what you were thinking. If you could have said it, that was lost signal.
  2. Mark every overclaim. “Always”, “never”, “thread-safe”, “it has no cost”. Note whether you corrected it and who noticed first.
  3. Check the first sentence of each answer. Did it answer the question, or warm up to it?
  4. Score each round on the rubric, then subtract one point in any column where you were generous. Solo scoring drifts upward; correct for it on purpose.
  5. Write one correction. Not five. One change to the next attempt, stated as a behaviour: “State the conflict rule in the first two minutes of the design round.”

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.

What changed in 2024 to 2026

  • Swift 6.2 shipped on 15 September 2025 with what the Swift team calls approachable concurrency (Swift.org, Swift 6.2 released).
  • A module can opt in to main actor isolation by default, so its code runs on the main actor without @MainActor written on every type.
  • The new @concurrent attribute marks a function that should run concurrently, which makes it visible in the code when work leaves the actor.

Sources

What to do next

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.

Free guide: the Phoenix Method