Live coding · 14 min
Mobile live coding interview: what to say while you type
How a mobile live coding round is scored, a 45-minute plan, what to say at each stage, a worked Swift example, and how to recover when you stall.
iOS · 12 min read
What an iOS coding interview asks: the practical tasks that recur, the algorithm round in Swift, and what is graded beyond whether it compiles.
This guide is for iOS engineers with a coding round coming up who want to know what they will be asked to build. It covers how to tell a practical round from an algorithm round before the call, the tasks that recur in iOS coding interviews with what each one is really testing, two worked tasks in Swift, what is graded beyond “it compiles”, and the Swift and Xcode traps that cost points under a clock.
The one thing to take from it: in a practical iOS task the grade is mostly set in the first ten minutes, by how you shape the model and what you say about cancellation and stale results. The UI is rarely what decides it.
There are two formats, and preparing for the wrong one wastes the week.
| Format | Environment | What it asks | What it rewards |
|---|---|---|---|
| Practical | Xcode with your screen shared, frequently on a starter project they give you | Fetch, parse, display, cache; a feature on existing code | Fluency with the simulator and debugger, state and cancellation, a working vertical slice |
| Algorithmic | A shared editor that compiles Swift with no UIKit or SwiftUI available | Language, concurrency and data structure problems | Clean standalone Swift, complexity you can state, a trace before “done” |
| Take-home | Your own machine, a few hours, then a review call | A small app to a brief | Scope discipline, tests on the logic, a README that names trade-offs |
The invitation rarely says which. “App building exercise” and “build a small app” usually mean practical. “Pairing session” can mean practical work on starter code, or a code review; the pair programming guide covers how that round is run. “Algorithms and data structures, but not LeetCode” usually still means algorithm practice.
Ask the recruiter with forced choices, because an open “what should I prepare?” gets a vague answer: “Will I write new code from scratch, work in a starter project you provide, or review existing code? Should I have Xcode ready, or will we use a browser editor? Is the emphasis on finishing a feature, or on discussing decisions as I build?”
These are the practical tasks that come back again and again in iOS coding rounds. Each one hides a single question the interviewer is really asking.
| Task | What it is really testing | The follow-up that comes next |
|---|---|---|
| Search as you type, without spamming the API | Out-of-order responses: a slow result for “sw” must not overwrite the result for “swift” | “Test the debounce without real delays.” Inject a clock. |
| An image loader for a scrolling feed | Cache before network, and one download per URL when two cells ask at once | “What happens when a cell is reused mid-load?” |
| A paginated feed | Explicit load states, and not firing “load more” twice | “The API changes from page numbers to a cursor.” |
| JSON to models to a list | Decoding resilience: dates, missing fields, unknown enum values | “One corrupt element. Does the whole feed fail?” |
| Token refresh under concurrency | Fifty callers get a 401 at once; exactly one refresh should run | “The refresh itself fails. What do the waiters see?” |
| A thread-safe LRU cache | O(1) get and set, then the actor versus lock trade-off | “It sits on a synchronous hot path. Now what?” |
| A rate limiter | A sliding window with an injectable clock | “A million calls a second.” Memory, not correctness, is now the problem. |
If you can build these from a blank file in Swift, out loud, on a timer, you have covered most of what a practical round asks. The mobile live coding guide works through the rate limiter minute by minute.
The prompt: “Requests carry a bearer token. When it expires, refresh it. Many requests can fail at the same moment.”
What the interviewer is waiting for is one sentence, said before you type: “An actor alone is not enough, because another caller can enter while the first is suspended on the network, so the refresh itself gets stored as a task, and anyone who arrives mid-refresh awaits that one task.”
struct Token: Sendable { let value: String; let expiresAt: Date }
protocol AuthClient: Sendable {
func refresh() async throws -> Token
}
actor TokenStore {
private var token: Token?
private var refreshing: Task<Token, Error>?
private let client: any AuthClient
init(client: any AuthClient) { self.client = client }
func validToken() async throws -> Token {
if let token, token.expiresAt > .now { return token }
if let refreshing { return try await refreshing.value }
let task = Task { try await client.refresh() }
refreshing = task
defer { refreshing = nil }
let fresh = try await task.value
token = fresh
return fresh
}
}This type-checks in Swift 6 language mode (checked with Swift 6.3.2). What gets graded, in order:
defer clears the stored task on success and on failure. Leave it out and one failed refresh poisons every caller after it, because they all await the dead task.The follow-up is usually “what if the refresh returns 401 too?” That is the moment to say the session is gone and the app must route to sign-in, rather than retry forever.
The prompt: “Query this search API as the user types. Do not call it on every keystroke.”
The clarifying question that decides the task: “Can responses arrive out of order?” Debouncing alone does not stop a slow response to an old query from landing after a fast response to a new one.
struct City: Identifiable, Sendable { let id: String; let name: String }
protocol CitySearch: Sendable {
func places(named term: String) async throws -> [City]
}
@MainActor
@Observable
final class SearchModel {
private(set) var results: [City] = []
private(set) var failed = false
private let search: any CitySearch
private var current: Task<Void, Never>?
init(search: any CitySearch) { self.search = search }
func queryChanged(_ text: String) {
current?.cancel()
let term = text.trimmingCharacters(in: .whitespaces)
if term.isEmpty { results = []; return }
current = Task {
do {
try await Task.sleep(for: .milliseconds(300))
let matches = try await search.places(named: term)
try Task.checkCancellation()
results = matches
failed = false
} catch is CancellationError {
// A newer query owns the screen now.
} catch {
failed = true
}
}
}
}This also type-checks in Swift 6 language mode. Say this while you write it: the sleep is the debounce. Task.sleep throws when cancelled, so typing again inside the window ends the old search before any request goes out. The checkCancellation() that follows the network await handles ordering: a task that has been replaced never writes its results. On cancellation the task does not touch the UI state at all, because the newer task owns it. Because the whole model is @MainActor, results cannot race.
The view is a TextField and a List. Write it if there is time. Describe it if there is not. Nobody fails this task for a missing view. Candidates fail it for the stale result.
The follow-up is almost always “now test the debounce”, and a hard-coded 300 milliseconds cannot be tested without waiting. Take the timing as a dependency instead: an any Clock<Duration> and a debounce Duration in the initialiser, with ContinuousClock() and 300 milliseconds as the defaults, and clock.sleep(for:) in place of Task.sleep. The test runs the same code with the window shrunk to a few milliseconds, sends three keystrokes back to back, and asserts that the stubbed service saw exactly one request. After that the ladder usually climbs: skip a query identical to the last one, replace the flags with an explicit state enum, then share the logic between screens. Knowing the next rung before it is asked is the senior version of this task.
Whatever the rubric calls them, interviewers score four things: problem decomposition, correctness, communication, and testing instinct. In a practical iOS task they show up like this:
Nobody runs a programming contest in an iOS screen. Expect mostly hash-map work: tallying, bucketing, finding a matching pair. After that come string problems, pointer and window scans over arrays, walking a tree (in iOS that tree is usually nested JSON or a set of views), and a stack for undo or matching brackets. Know sorting well enough to quote O(n log n) and call sorted(by:) cleanly. A linked list, when one shows up, is usually hiding inside an LRU cache. Heavy graph problems and dynamic programming are uncommon in iOS loops; spend the week elsewhere unless the company is known for one generic screen across all engineering roles.
Most of the cheap mistakes are Swift-specific:
Set or Dictionary answers in constant time on average, while contains and first(where:) scan the array. Call either inside a loop and your O(n) answer is suddenly O(n²). If the prompt mentions “seen before” or “distinct”, start with a Set.array[1...] still counts from the parent’s indices, not from zero. Binary search is where that usually goes wrong under time pressure.String with an Int. Turning it into [Character] first is the fast route on a timer; say that you know the index-based API exists and why you are skipping it.Twenty to thirty problems across those categories, each solved in Swift from an empty file, out loud, on a 25-minute timer, is enough. Solving an old one again, cleanly, seven days on teaches more than three fresh problems.
A take-home trades pressure for time, and reviewers grade scope as hard as code. Spending three times the suggested hours looks like a scoping problem, not enthusiasm, and it works against you: the reviewer now judges the result as the work of those extra hours.
Use the time they state and spend it in priority order: first a core that works from end to end, next tests around the logic that carries risk, next the README, and polish only with time left over. The review leans on the README: what the app is and the command to run it, one paragraph of architecture and why it suits a problem this small, two or three trade-offs, what you would do with more time, and how long you spent. Assume a review call where every line you submitted will be questioned, including anything an AI tool wrote for you.
@ObservedObject for an object the view creates. It is recreated when the parent re-evaluates and loses its state.prepareForReuse is not enough on its own; check the cell still wants this image before setting it.UIImage in an environment where UIKit does not exist. Model the data and say where the view would go.If the coding round is one of several in your loop, iOS interview books compares the books by round and by the time you have left.
@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.
Live coding · 14 min
How a mobile live coding round is scored, a 45-minute plan, what to say at each stage, a worked Swift example, and how to recover when you stall.

iOS · 11 min
What an iOS technical interview loop contains, what each round scores, and where strong Swift candidates still lose it.

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.