Articles
iOS Interview Questions for Senior Engineers: Swift Concurrency, Architecture, and Performance
Prepare for senior iOS interviews with practical Swift concurrency, architecture, and performance questions, strong answers, and a focused 5-day plan.
Two candidates can both explain async/await. The senior candidate is the one who notices that an image decode still runs on the main actor, that cached state can change while an actor is suspended, and that a "clean" architecture is hurting launch time.
That is the level senior iOS interviews are designed to uncover. You are rarely being tested on Swift syntax alone. You are being asked to define ownership, make concurrency safe, choose boundaries that a team can maintain, and prove performance claims with evidence.
Filter by your target company, answer aloud, and compare your decisions with the follow-ups a real interviewer is likely to ask.
Quick Answer: What Do Senior iOS Interviews Test?
Senior iOS interviews test whether you can turn product constraints into a reliable mobile system. Swift concurrency, architecture, and performance are connected: an unclear state owner creates unsafe tasks, and unsafe task boundaries often become responsiveness or memory problems.
| Interview area | What a senior answer demonstrates |
|---|---|
| Swift concurrency | Defines isolation, lifetime, cancellation, and data transfer explicitly |
| Architecture | Chooses boundaries from requirements instead of naming a favorite pattern |
| Performance | Measures first, isolates the bottleneck, and verifies the result |
| Delivery | Explains migration, testing, observability, and team ownership |
A strong answer follows a repeatable shape: clarify the constraint, name the state owner, draw the execution boundary, identify failure modes, and state how you would measure success.
Swift Concurrency Interview Questions
1. What should be isolated to the main actor?
UI state and operations that must coordinate with UIKit or SwiftUI belong on @MainActor. Network requests, image decoding, database work, and expensive transformations should not be placed there merely because a view model owns the result.
A senior answer separates where state is owned from where work executes. Fetch and transform data outside the UI isolation domain, return a safe value, then update the UI-owned state on the main actor. Also mention that moving work off the main actor should be driven by profiling, not annotation folklore.
2. What does Sendable protect?
Sendable describes values that can safely cross concurrency domains. Value types composed of sendable values are natural candidates. Mutable reference types require isolation, immutability, or carefully implemented synchronization.
@unchecked Sendable is not a compiler escape hatch to apply until warnings disappear. It transfers the proof obligation to you. In an interview, explain the lock, queue, actor, or immutability rule that makes the unchecked conformance true.
3. Structured or unstructured concurrency?
Prefer async let or a task group when child work belongs to the current operation. Structured tasks inherit useful context, cannot outlive their scope, and propagate cancellation through the task hierarchy.
Use an unstructured Task when work needs an independently managed lifetime, such as a user-triggered load owned by a screen. Store its handle, define who cancels it, and avoid Task.detached unless you intentionally want to leave the current actor and task context.
4. How should cancellation work in an iOS feature?
Cancellation is cooperative. Canceling a task records intent; the operation must reach a cancellation-aware suspension point or check cancellation itself. A scrolling feed should cancel image work for content that is no longer useful, but it should not corrupt a shared cache when one consumer disappears.
Define cancellation ownership from the UI lifecycle and product behavior. A search task can be replaced when the query changes, a refresh can be canceled when the screen disappears, and shared work may need reference counting or a separate owner.
5. What is actor reentrancy, and why does it matter?
An actor protects mutually exclusive access while code is executing, but an actor method can suspend at await. Other work may then run on that actor before the original method resumes. The state is race-free, yet an assumption made before suspension may no longer be valid afterward.
actor ImageCache {
private var generation = 0
private var images: [URL: UIImage] = [:]
func replace(_ data: Data, for url: URL) async throws {
let expectedGeneration = generation
let image = try await decoder.decode(data)
try Task.checkCancellation()
guard generation == expectedGeneration else { return }
images[url] = image
}
func removeAll() {
generation += 1
images.removeAll()
}
}
The generation check is a business invariant, not a data-race fix. A strong candidate explicitly revalidates state after suspension instead of assuming actor calls execute in strict FIFO order.
6. How do you bridge a callback API to async code?
Use a checked continuation when an API cannot yet be expressed asynchronously, and guarantee that every path resumes it exactly once. Then decide how cancellation reaches the underlying operation; wrapping a callback does not automatically make that work cancellable.
Tests should cover success, error, cancellation, and the callback never arriving. If the API emits many values, an AsyncSequence is usually a better model than repeatedly creating one-shot continuations.
iOS Architecture Interview Questions
7. Design an image-heavy feed that works offline
Start with product behavior: freshness, pagination, offline reads, mutation conflicts, memory limits, and acceptable stale content. Then identify one source of truth for feed state and separate transport models, persisted records, domain values, and view state where those distinctions reduce coupling.
A practical design might use a repository to coordinate network and local storage, an image pipeline with bounded memory and disk caches, and a UI-facing state owner on the main actor. Deduplicate in-flight requests, cancel low-value prefetches, and make cache keys include every transformation that changes the result.
The senior signal is not drawing more boxes. It is explaining which component owns consistency, where backpressure occurs, what happens during partial failure, and how the design evolves when usage grows.
8. MVVM, Clean Architecture, or unidirectional data flow?
There is no universally correct pattern. MVVM can be sufficient for a focused feature, unidirectional flow helps when state transitions and replayability matter, and additional domain layers can protect complex business rules from framework churn.
Choose based on change frequency, test boundaries, team size, and feature coupling. If a pattern creates pass-through protocols and mappers with no independent policy, simplify it. If views directly coordinate persistence, networking, analytics, and navigation, introduce a boundary with a clear owner.
9. How would you modularize and migrate a large UIKit app?
Use product or capability boundaries that can own code, tests, and release decisions. Avoid splitting modules only by technical layers if every feature change then touches five packages and creates a wide dependency graph.
For a UIKit-to-SwiftUI migration, replace screens or flows behind stable navigation and data contracts. Keep interoperability explicit, measure binary and launch impact, and avoid maintaining two competing state owners. A safe migration produces incremental user value and has a rollback path.
iOS Performance Interview Questions
10. A SwiftUI feed stutters while scrolling. What do you do?
First reproduce the issue on a representative device and record a baseline. Use Instruments to distinguish a busy or blocked main thread, long SwiftUI view updates, excessive invalidations, image decoding, layout work, and rendering hitches.
Possible fixes include decoding and resizing images before display, limiting prefetch concurrency, narrowing observed state, stabilizing item identity, moving expensive calculations out of body, and avoiding synchronous disk or network work on the main thread. State which trace would confirm your hypothesis before changing the code.
11. Memory grows after repeatedly opening a screen. Is it a leak?
Not necessarily. It may be a retain cycle, an unbounded cache, retained decoded images, queued work, or allocator behavior. Compare memory graphs before and after the flow, inspect object ownership, and use Allocations generations and Leaks to separate live growth from temporary allocation churn.
Check closures, delegates, Combine subscriptions, tasks capturing owners, notification observers, and caches. A senior response also defines an appropriate cache budget and tests the feature under memory pressure instead of treating every retained byte as a leak.
12. How do you improve launch time and production performance?
Measure cold and warm launch separately on target devices. Use the App Launch and Time Profiler templates to find framework loading, synchronous initialization, database migration, dependency construction, and first-frame work. Defer optional work and reduce dependencies only after identifying their measured cost.
Local profiling is one view. Xcode Organizer and MetricKit add aggregate evidence from shipped builds, including launch, responsiveness, CPU, memory, hangs, and other diagnostics. Define a performance budget, segment by device and app version, and verify that an optimization improves the user-facing metric without moving cost elsewhere.
A Senior-Level Design Answer, Step by Step
Prompt: Design the home feed for a media app. It streams updates, supports offline reading, and must scroll smoothly on older devices.
1. Clarify the contract. Ask about freshness, ordering, pagination, offline edits, image sizes, supported devices, and failure behavior. Convert "smooth" into a measurable interaction goal and identify the lowest device class that matters.
2. Establish ownership. Put UI state on the main actor. Let a repository coordinate network and persistent data. Give the image pipeline its own bounded cache and request-deduplication policy. Pass immutable or sendable values across isolation boundaries.
3. Control work. Use structured tasks for one refresh, cancel obsolete searches and prefetches, limit concurrency, and revalidate state after every meaningful suspension. Keep cached data available when refresh fails and surface a recoverable error without blanking the feed.
4. Prove the design. Profile scrolling, memory, launch, network transfer, and energy on representative devices. Add performance tests for critical flows and production metrics for regressions. Explain which trade-off you would revisit if product requirements change.
How Interviewers Score Senior iOS Answers
| Weak signal | Strong senior signal |
|---|---|
| "Use async/await so it is off the main thread" | Names isolation and execution boundaries separately |
| "Actors prevent every concurrency bug" | Rechecks invariants across suspension and designs cancellation |
| Chooses MVVM by default | Derives boundaries from ownership and change patterns |
| Optimizes from intuition | Profiles, forms a hypothesis, changes one variable, and verifies |
| Focuses only on implementation | Covers migration, testing, rollout, observability, and team impact |
A Focused Five-Day Practice Plan
Day 1: explain actors, @MainActor, Sendable, task hierarchy, cancellation, and reentrancy aloud. For each concept, give one production failure it prevents and one problem it does not solve.
Days 2-3: design an offline feed and one transaction-heavy feature. Draw state ownership, dependencies, storage, navigation, isolation domains, and degraded behavior. Use company-specific interview prep to select prompts close to your target loop.
Day 4: rehearse three diagnoses: scrolling hitch, growing memory, and slow launch. Name the tool, trace, hypothesis, change, and verification metric for each.
Day 5: run a 45-minute mock. Spend five minutes clarifying, 25 minutes designing, and the remainder on concurrency, performance, testing, and trade-offs. Finish with behavioral and leadership practice about a difficult migration, incident, or performance decision you led.
Frequently Asked Questions
Does @MainActor automatically move expensive work to a background thread?
No. @MainActor isolates code to the main actor; it is not a background-work primitive. Keep UI mutations there, but design expensive independent work so it can execute outside that isolation domain and return a safe result.
Do actors eliminate all race conditions?
Actors eliminate unsynchronized access to their isolated mutable state. They do not eliminate higher-level logic races. State can change while an actor method is suspended, so invariants often need to be checked again after await.
Which iOS architecture should I say is best?
Do not claim one pattern is universally best. Explain the requirements, state owner, dependency direction, test boundary, migration cost, and team trade-offs that make a particular structure appropriate.
Should I mention Instruments even if the interview has no profiler?
Yes. You do not need to recite every instrument. Name the measurement that would distinguish your hypotheses, such as Time Profiler for main-thread work, Allocations for memory growth, or the SwiftUI instrument for long and frequent updates.
Turn iOS Knowledge Into Interview Evidence
The best senior answers are not the longest. They make ownership visible, connect language guarantees to product behavior, and replace performance guesses with a measurement plan.
Answer first, inspect the solution second, and keep a short record of every assumption an interviewer was able to challenge.