iOS · 15 min read

Senior iOS mock interview: a full loop for 2026

A senior iOS mock interview loop: four 45 to 60 minute rounds, escalating follow-ups, senior versus mid-level answers, a rubric and a debrief template.

A senior iOS mock interview is not a longer mid-level mock. The prompts can look almost identical. What changes is the follow-up ladder: the interviewer keeps pushing until the answer breaks, and the score is the rung where it broke. A mid-level candidate is expected to answer the question. A senior candidate is expected to answer the question behind it, name the constraint that would change the answer, and stay calm while the interviewer changes it.

This page is a full senior loop you can run with a partner or alone against a recording: four rounds of 45 to 60 minutes, prompts with three levels of follow-up, what a senior answer shows compared with a mid-level one, a rubric and a debrief template. I wrote it from the interviewer’s chair, after more than 500 technical interviews, so the notes describe what I write down.

The loop at a glance

A typical senior loop looks like this; companies differ in the number of rounds, their length and their order.

Round Length Main prompt What decides it
1. Concurrency deep dive 45 min A shared cache hit by many tasks Precision under restatement
2. Live coding 60 min A token manager with single-flight refresh Test first, narrate, recover
3. System design 60 min Offline-first notes with sync Scope, source of truth, conflicts
4. Behavioural and leadership 45 min A migration you led Your role, cost, a number

Run one round per day if you can. Four hours in one sitting teaches endurance, which matters for a real onsite, but it also blurs the debrief. Two short rules for the interviewer: never teach during the round, and always climb the follow-up ladder, even when the first answer is excellent. Feedback belongs after the attempt.

Round 1: Concurrency deep dive, 45 minutes

1. “Many tasks read and write one cache. How do you make it safe?”

Level 1 follow-up: “So an actor makes it thread-safe, done?” This is a restatement, deliberately too strong. The senior move is to narrow it: an actor removes data races on its own state, and it does not protect an invariant across an await.

Level 2 follow-up: “Show me the bug.” Two callers miss, both start the same download. The fix is storing the in-flight task so the second caller joins the first.

Level 3 follow-up: “Now 40 screens request a token at once and it has expired. What happens?” This is the same pattern at the network edge: a refresh stampede. A senior answer puts a single refresh task behind the actor and has every caller await it.

Senior answer: “An actor is the right home, but it only guarantees exclusive access between suspension points. Anything I check before an await I re-check after it, or I make the in-flight work itself the shared state.”

Mid-level answer: “An actor serialises access, so there are no races.” Correct as a sentence. It does not survive level 1, and the candidate usually agrees with the interviewer’s overstatement instead of correcting it.

2. “Your module compiles cleanly in Swift 6 language mode. Is it free of concurrency bugs?”

Level 1: “Then why keep @unchecked Sendable anywhere?” Level 2: “A long task ignores cancellation. Where does that show up?” Level 3: “When would you use Task.detached?”

Senior answer: “Swift 6 mode turns data races into compile errors. It does not catch logic races across suspension points, missing cancellation checks, or a swallowed CancellationError. @unchecked Sendable is legitimate only for a type that protects itself with a lock I can point to. Task.detached drops the caller’s priority, task-local values and actor context, so I use it rarely and on purpose.”

The pattern in round 1 is restatement. The interviewer says your answer back a little too strong, three or four times, and scores whether you accept the stronger version. Practise hearing it. A calm “not quite, here is the precise version” is the single most senior sentence in this round.

Round 2: Live coding, 60 minutes

3. “Write a token provider that returns a valid token, refreshes when it is missing or expired, and is safe when many callers ask at once. Start with a failing test.”

Split the hour roughly like a real round: five minutes clarifying, thirty building the core, ten testing and discussing trade-offs, and the rest on follow-ups. Ask about the clock, the refresh failure case, and whether callers can be cancelled before you write code.

actor TokenProvider {
    private var current: (value: String, expiry: Date)?
    private var refreshing: Task<(value: String, expiry: Date), Error>?
    private let refresh: @Sendable () async throws -> (value: String, expiry: Date)
    private let now: @Sendable () -> Date

    init(refresh: @escaping @Sendable () async throws -> (value: String, expiry: Date),
         now: @escaping @Sendable () -> Date = { Date() }) {
        self.refresh = refresh
        self.now = now
    }

    func token() async throws -> String {
        if let current, current.expiry > now() { return current.value }
        if let refreshing { return try await refreshing.value.value }
        let work = Task { try await refresh() }
        refreshing = work
        defer { refreshing = nil }
        let fresh = try await work.value
        current = fresh
        return fresh.value
    }
}

Level 1 follow-up: “Prove that ten concurrent callers cause one refresh.” A counting fake for refresh, a task group of ten calls, one assertion on the count.

Level 2 follow-up: “The refresh fails. What do the ten callers see, and what does the eleventh do?” All ten get the error; the eleventh starts a new refresh because the failed task was cleared. Say whether that is what you want, and when you would add backoff.

Level 3 follow-up: “The server returns 401 on a request that used a token you believed was valid.” Now the provider needs a way to invalidate the current token and force one refresh, without every failing request triggering its own.

What a senior answer shows: the failing test written first, the clock injected so expiry is testable, the stampede solved on purpose rather than by luck, and the failure path discussed before the interviewer asks. What a mid-level answer shows: working code for one caller, a race discovered only when the interviewer asks about concurrency, and a test added at the end if time allows.

A stall in this round is normal. What gets scored is what you do next: say where you are stuck, state the simplest thing that would move you forward, and take a hint if one is offered. Silent struggling looks the same as silent confusion from the other side of the screen.

Round 3: System design, 60 minutes

4. “Design offline-first notes for iPhone and iPad, with sync across a user’s devices.”

Level 1 follow-up: “What is the source of truth?” The local store, for everything the UI shows. The server is the source of truth for what other devices will eventually see.

Level 2 follow-up: “The same note is edited on two devices while both are offline.” Last write wins silently destroys text here. A senior answer picks a rule, such as per-field merge or keeping both versions for the user, and says what it costs.

Level 3 follow-up: “The app is killed halfway through uploading the outbox.” Operations need identifiers so the server can deduplicate a retry, and the outbox entry is removed only after the server confirms it.

What a senior answer shows in the first ten minutes: scope agreed out loud, the data flow drawn (UI reads local store, writes go to the store and an outbox, a sync process drains it), and a named conflict rule. What a mid-level answer shows: a screen-by-screen tour, a networking layer, and a conflict question that arrives as a surprise at minute 40.

Choose persistence with the same judgement. SwiftData suits a new app with a straightforward model; Core Data still gives finer control for complex migrations. Saying which you would pick for this app, and what would change your mind, is worth more than naming both. The deeper version of this round is in iOS system design interview.

Round 4: Behavioural and leadership, 45 minutes

5. “Tell me about a large migration you led.”

Level 1 follow-up: “What was your part, not the team’s?” Level 2: “What did it cost, and who objected?” Level 3: “What number moved, and what is still not done?”

Senior answer shape: the context in two sentences; why a rewrite was the wrong plan and how you made it incremental; who you brought along and how you handled the disagreement; a number that changed; the part that remains unfinished and why. Ownership of a failure, and the process change that followed, belongs in the story.

Mid-level answer shape: “we” throughout, the migration described as a technical task, no cost, and a result stated as “it went well”.

6. “A release crashes for a slice of users. Walk me through the first hour.”

Stop the damage first: pause the phased release, turn off the flag if the crash sits behind one. Then read the crash reports for the common pattern, choose between a server change, a flag and an expedited build, and say who you tell and when. End with the check that would have caught it. Senior loops probe this operational judgement more than most candidates expect, and a candidate who opens with “I would debug the code” has skipped the part the business cares about.

The rubric

Score every round on the same four columns, 1 to 5 each. The difference between levels is in the right-hand cells.

Column Mid-level (3) Senior (4) Strong senior (5)
Signal Correct answer, general example Production detail in the first answer Names the failure mode before being asked
Structure Understandable with some prompting Clear order, easy to follow Sets the agenda for the round and keeps it
Depth Holds level 1 Holds level 2 Holds level 3 and says where the edge is
Judgement Changes the answer when told to Names the constraint that would change it Weighs two options, picks one, states the cost

Four rounds, four columns, maximum 80. A total does not predict an offer, so do not treat it as a threshold. Read the rubric for patterns: a candidate at 5 on signal and 2 on structure has a communication problem, not a knowledge problem, and needs a different week of practice than the reverse.

The debrief template

Fill this in straight after each round, before looking at any reference answer. If a partner ran the round, both of you fill it in separately, then compare. Discuss any column where your scores differ by more than one point.

Round:                      Date:            Length:
Prompt:
Highest follow-up level reached (1, 2, 3):
Where the answer broke, in one sentence:

Signal     _/5  evidence:
Structure  _/5  evidence:
Depth      _/5  evidence:
Judgment   _/5  evidence:

Overclaims made, and whether I corrected them:
Longest silence, and what I was thinking:
The one behavior to change next time:
Re-run date:

The evidence lines matter more than the numbers. “Depth 3” tells you nothing a week later. “Depth 3: could not say what the eleventh caller does after a failed refresh” tells you exactly what to rehearse. Write evidence first, number second.

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 one round this week with a partner who is willing to push. Write the debrief with evidence, pick one behaviour, and run the same round again. The senior loop rewards the candidate who has already felt the third follow-up, so make sure you have.

Free guide: the Phoenix Method