Preparing for an iOS engineering interview is very different from memorizing Swift syntax.
Most experienced interviewers are not trying to discover whether you remember every property wrapper or every URLSession method from memory. They want to understand how you think when an application becomes complex, when requirements change, when production crashes appear, when concurrency causes unexpected behavior, or when a feature has to survive years of maintenance.
A strong iOS engineer should be able to explain not only what a technology does, but also:
- why it was chosen,
- what alternatives were considered,
- what trade-offs were involved,
- how the implementation was tested,
- how production behavior was measured,
- and what could be improved next time.
This guide covers the areas I would focus on when preparing for a modern iOS technical interview in 2026.
For a deeper look at production project structure, see How Should a Modern iOS Project Be Structured?.
What Do iOS Interviews Usually Evaluate?
A typical iOS interview evaluates several engineering dimensions at the same time.
You may be asked about Swift and Swift Concurrency, but the interviewer is also evaluating architecture, debugging ability, communication, product thinking, performance awareness, testing discipline, and ownership.
For mid-level and senior positions, the discussion often moves beyond isolated coding questions.
Instead of:
What is
async/await?
you may get something closer to:
A screen starts three network requests. The user navigates away halfway through. How would you structure the concurrency and cancellation behavior?
Instead of:
What is MVVM?
you may hear:
Your ViewModel has grown to 1,500 lines and owns networking, persistence, formatting, navigation, and analytics. How would you improve it without rewriting the entire feature?
That difference is important.
The interview is testing engineering judgment.
The Areas You Should Prepare
For a modern iOS interview, I would prepare these topics carefully:
Swift fundamentals, including value vs reference semantics, protocols, generics, optionals, closures, ARC, error handling, and access control.
Swift Concurrency, including async/await, Task, structured concurrency, task groups, cancellation, actors, @MainActor, Sendable, and data-race prevention.
SwiftUI, including state management, @State, @Binding, @Environment, Observation, view identity, rendering, navigation, reusable components, and interoperability with UIKit.
UIKit, because many real production applications still contain large UIKit codebases.
Architecture, including MVVM, Clean Architecture, coordinators, dependency injection, repositories, modularization, and feature boundaries.
Networking, including URLSession, REST APIs, authentication, retries, pagination, caching, error handling, and idempotency.
Persistence, including SwiftData, Core Data, Keychain, files, caching, migrations, and offline-first design.
Testing, including Swift Testing, XCTest, unit tests, integration tests, UI tests, dependency injection, mocks, fakes, and regression tests.
Performance, including Instruments, memory leaks, retain cycles, unnecessary SwiftUI rendering, startup performance, scrolling performance, and networking.
Production engineering, including crash reporting, analytics, logging, feature flags, release monitoring, App Store deployment, background execution, and debugging issues that cannot easily be reproduced locally.
Do Not Answer Everything Like a Textbook
One of the biggest mistakes in technical interviews is giving technically correct but completely abstract answers.
For example:
MVVM separates the View from the business logic using a ViewModel.
That definition is correct, but it does not tell the interviewer whether you can actually use MVVM.
A stronger answer would explain a situation.
For example:
In one media application, the ViewController was responsible for playback state, networking, persistence, and UI updates. That made the screen difficult to test and caused lifecycle bugs. I moved the durable screen state into a ViewModel, introduced repositories for remote and local data, and kept the view focused on rendering state and forwarding user actions. That also allowed most of the business logic to be unit tested without launching the UI.
That answer shows architecture, reasoning, migration strategy, and testing.
A useful structure is:
**Context → Problem → Decision → Implementation → Validation → Result**
You do not need to say “Situation”, “Task”, “Action”, and “Result” during the interview.
The answer should sound like a natural engineering conversation.
30 iOS Technical Interview Questions and Realistic Sample Answers
The strongest answers in an iOS interview usually combine two things: a clear technical explanation and evidence that you have applied the concept in a real project.
The examples below follow a natural STAR-style structure without explicitly saying Situation, Task, Action, and Result. Each answer first addresses the technical question, then moves into a realistic project scenario:
Technical answer → Project context → Problem → Decision and implementation → Validation → Result
The goal is not to memorize these answers word for word. Use them to learn how to connect iOS concepts to real engineering experience.
1. Tell me about a recent iOS project you worked on.
A good answer should quickly establish the product, your responsibilities, the technologies you used, and one engineering problem you personally solved.
I recently worked on a media-processing iOS application using Swift, SwiftUI, AVFoundation, and an Objective-C++ bridge for native functionality. The application allowed users to import media, apply processing settings, preview the result, and export it.
One challenge was keeping the UI, playback state, and processing configuration synchronized. Initially, some state was owned by the SwiftUI views while other state lived inside the media-processing layer, which made features such as preset switching and A/B comparison difficult to reason about.
I moved the durable editor state into a dedicated ViewModel and kept the processing engine behind a separate service boundary. SwiftUI became responsible mainly for rendering state and forwarding user actions.
I tested the flow with repeated media imports, preset changes, background and foreground transitions, and export operations.
The result was a clearer ownership model and significantly easier feature development because UI changes no longer required modifying the media-processing code.
That answer tells the interviewer much more than:
“I built an app with SwiftUI.”
2. What is the difference between a struct and a class in Swift?
struct uses value semantics, while class uses reference semantics and has identity.
In practice, I normally prefer structs for models and UI state when I do not need shared identity.
In one SwiftUI application, I represented the editor configuration as a struct containing values such as volume, EQ settings, stereo width, and preset information.
Earlier versions of the feature relied on mutable shared objects, and it was difficult to understand which component had modified the current configuration.
Moving the editor state to value types made each state transition explicit. When the user selected a new preset, for example, I could create an updated state while deliberately preserving values that should not change.
Long-lived components such as the audio-processing engine remained classes because they had identity and managed external resources.
That separation made ownership much easier to understand and reduced accidental state mutation.
The important point is not that structs are always better.
The decision depends on whether the object represents a value or an identity with a lifetime.
3. How would you investigate a memory leak in an iOS application?
I would first verify that an object is actually staying alive, then inspect its ownership chain using tools such as Xcode Memory Graph and Instruments.
In a media application, imagine that memory usage keeps increasing every time the editor screen is opened and closed.
My first step would be to confirm whether the screen's ViewModel or underlying media objects are being deallocated. I would temporarily inspect
deinitand then use Memory Graph to find who still owns the object.A common cause in this type of feature could be a long-lived player or service storing a callback that captures the ViewModel strongly.
I would fix the ownership relationship rather than simply adding
[weak self]everywhere, then repeat the navigation and media-loading scenario while monitoring allocations.The expected result would be that the editor and its media resources are released after leaving the screen and memory returns to a stable level.
This shows that you understand both ARC and how to diagnose a real production problem.
4. When would you use weak and unowned?
I use weak when the referenced object may disappear before the owner of the reference. I use unowned only when the lifetime relationship guarantees that the referenced object will still exist.
For example, in a media-processing application I may have a long-lived processing service that reports progress back to a screen.
The screen can disappear while processing infrastructure still exists, so the callback should not strongly keep the screen alive.
I would normally use a weak reference there and safely ignore the callback if the UI no longer exists.
unownedwould only be appropriate if I could guarantee the referenced object always outlives the holder.I tend to be conservative with
unownedbecause if that lifetime assumption changes later, the result is a crash rather than simplynil.
5. How does ARC work?
ARC tracks strong references to class instances and deallocates an object after the final strong reference disappears.
The main production issue is usually not ARC itself but retain cycles.
In applications involving media playback, I pay particular attention to players, timers, notification observers, closures, subscriptions, and asynchronous callbacks because these components often have longer lifetimes than the screen.
For example, if a playback service owns a progress callback and that callback captures the ViewModel strongly, the service and ViewModel can unintentionally keep each other alive.
I would inspect the ownership chain, break the cycle at the correct architectural boundary, and verify the fix with Memory Graph and repeated navigation.
That is why I think about ARC primarily as an ownership-design problem rather than something the compiler completely handles for me.
6. What are the advantages of Swift Concurrency and async/await?
async/await makes asynchronous code easier to read, but its bigger advantages are structured lifetime, cancellation, and clearer error propagation.
In one application, media metadata and project information had to be loaded before the editor could become usable.
With callback-based code, loading state and errors could be updated from several different completion handlers. It also became possible for a callback to arrive after the user had left the screen.
I moved the workflow to async functions and attached the task to the feature that requested the work.
When the screen disappeared, the task could be cancelled rather than allowing stale work to update the interface.
I tested successful loading, network failure, cancellation, and rapid navigation.
The result was a much simpler lifecycle and fewer paths where stale asynchronous work could affect current UI state.
7. What is structured concurrency?
Structured concurrency means asynchronous operations belong to a defined parent task.
In a project, imagine the editor needs both remote project metadata and local media information before it can display the complete screen.
Because the operations are independent, I could run them concurrently using
async let.The important part is that both operations remain children of the same task.
If the user closes the project, cancelling the parent task also cancels the work that no longer matters.
That is preferable to launching unrelated tasks and later trying to determine which ones are still relevant.
The main benefit is predictable ownership.
8. What is the difference between Task and Task.detached?
A normal Task inherits important execution context such as actor isolation. Task.detached creates independent work.
In a SwiftUI application, most work I launch from a ViewModel belongs to the lifecycle of that feature.
For example, loading a project only matters while that project is still active. A regular task is therefore the natural choice.
I would not use
Task.detachedsimply to move something “to the background,” because it makes ownership and isolation harder to reason about.I would only use a detached task when the operation genuinely needs an independent lifetime and I have explicitly handled cancellation and concurrency safety.
9. What problem do actors solve?
Actors protect mutable state from concurrent access.
A practical example is an authentication service used by several API requests.
If multiple requests receive a 401 at almost the same time, they could all attempt to refresh the access token.
That can create several refresh requests and potentially race when updating credentials.
I would isolate token state inside an actor and allow callers to wait for the same refresh operation.
I would test the implementation by deliberately sending several requests with an expired token at the same time.
The successful result would be one refresh operation followed by all waiting requests continuing with the new token.
That demonstrates why an actor exists instead of merely defining one.
10. What does @MainActor do?
@MainActor isolates code to the main actor, which is particularly useful for state directly consumed by the UI.
In one SwiftUI project, the ViewModel exposed loading, error, project, and export state to several views.
Rather than manually dispatching every UI mutation back to the main queue, I isolated the ViewModel on the main actor.
Network and media operations could still suspend while doing asynchronous work, but state mutation resumed within the correct isolation context.
That made thread ownership explicit and removed a lot of scattered
DispatchQueue.main.asynclogic.I still kept expensive synchronous processing outside the main actor so it could not block the UI.
11. What is Sendable?
Sendable represents data that can safely move across concurrency domains.
When working with Swift concurrency, I found that immutable configuration models were easy to move between components because they were value types.
Problems appeared around services that contained mutable reference state.
Instead of immediately marking those classes
@unchecked Sendable, I first looked at whether that mutable state actually needed to cross the concurrency boundary.In several cases, keeping the state behind an actor or passing an immutable snapshot was safer.
I consider strict concurrency warnings useful because they often reveal unclear ownership that might otherwise become a production race condition.
12. What are the biggest advantages of SwiftUI?
SwiftUI gives you a declarative, state-driven UI model.
In one editor screen, several controls depended on the same configuration: sliders, preset selection, playback state, and processing values.
With imperative updates, every state change could require manually updating several controls.
I instead exposed one coherent editor state and let the SwiftUI hierarchy derive its interface from that state.
For example, choosing a preset updated the configuration, and all relevant controls automatically reflected the new values.
That reduced synchronization code and made it much harder for the UI to display values that disagreed with the actual processing state.
That is the benefit I emphasize over simply saying SwiftUI requires less code.
13. What is the difference between @State and @Binding?
@State is state owned by the current view. @Binding provides writable access to state owned elsewhere.
I had a reusable audio-control component containing a slider.
Initially it stored its own value, which worked until the parent also needed to modify that value when the user selected a preset.
Maintaining a local copy then created two potential sources of truth.
I moved ownership of the actual processing value to the parent state and passed a binding into the control.
After that, changes from either preset selection or direct user interaction were reflected consistently in the same underlying value.
14. What is state hoisting?
State hoisting means moving state to the component that actually needs to own it and passing value and actions down to children.
In a settings screen, imagine a child component owns a processing option internally, but that option also needs to change when the user switches profiles.
Keeping both parent and child copies requires synchronization logic.
I would move the state to their common owner and let the child render the value and report user actions.
This preserves a single source of truth.
I would not hoist purely visual state unnecessarily, though. Something like whether a temporary tooltip is expanded can still belong to the component itself.
15. What causes a SwiftUI view to update?
SwiftUI reevaluates a view when the state it depends on changes.
The important performance question is what work happens during that reevaluation.
In one list-based screen, filtering and sorting were originally performed directly inside the view body.
Functionally it worked, but changes in unrelated UI state caused the transformation to run again.
I moved the transformation into the ViewModel and exposed already prepared display state.
I then profiled the same interaction again.
The UI became more predictable and expensive work was no longer tied to view rendering.
This is why I try to keep SwiftUI bodies cheap.
16. How would you diagnose SwiftUI performance problems?
I would measure the problem first with Instruments and SwiftUI profiling tools instead of assuming the framework is slow.
For example, suppose a project list begins dropping frames with hundreds of items.
I would reproduce the issue using realistic data, profile CPU work and allocations, and inspect whether changing one item invalidates much more of the hierarchy than necessary.
I would then check identifiers, model stability, expensive calculations inside rows, image loading, and the granularity of observed state.
After each meaningful change, I would repeat the same scrolling scenario and compare the measurements.
That last step matters because an optimization is only useful if the measured behavior actually improves.
17. When would you still use UIKit?
UIKit is still a good choice for mature codebases, existing architecture, specialized components, and incremental migration.
If I joined an established application where navigation and several critical screens were stable UIKit code, I would not propose rewriting them simply because SwiftUI is newer.
I would identify feature boundaries where SwiftUI gives us clear value and introduce it incrementally, for example using
UIHostingController.Shared business and data logic should remain independent of either UI framework.
That approach reduces migration risk while still allowing the application to modernize over time.
For me, senior engineering includes knowing when not to rewrite working software.
18. What is MVVM?
MVVM separates presentation state from UI rendering.
In one feature, the screen had gradually accumulated networking, formatting, loading logic, persistence, and navigation behavior.
That made both testing and changing the screen difficult.
I moved data access into repositories and put presentation state and user actions into a ViewModel.
The SwiftUI view then became mainly responsible for rendering the current state and forwarding user interactions.
I could unit-test loading, success, error, and state transitions without running the UI.
The result was not just smaller files; the responsibilities became much clearer.
19. What is Clean Architecture?
Clean Architecture is mainly about keeping business rules independent from framework and infrastructure details.
In one application, API response models started leaking directly into the presentation layer.
That meant a backend schema change could force changes in multiple UI components.
For the feature where business rules were substantial, I introduced a domain representation and performed mapping at the data boundary.
That added some mapping code, but changes to networking became localized and domain tests no longer depended on transport models.
I would not apply that same amount of abstraction to a trivial screen because unnecessary layers also have a maintenance cost.
20. What would you do with a 1,500-line ViewModel?
I would first identify responsibilities rather than splitting the file arbitrarily.
I have seen feature classes grow because networking, validation, navigation, analytics, formatting, persistence, and UI state all ended up in the same place.
My first step would be to protect the existing behavior with tests.
Then I would extract responsibilities incrementally: repositories for data access, dedicated domain logic for reusable rules, navigation outside the ViewModel when appropriate, and infrastructure concerns such as analytics behind their own interfaces.
I would avoid a complete rewrite because a large mature feature usually contains behavior that is easy to accidentally lose.
The result I want is not a certain number of lines. I want components that can evolve independently.
21. How would you design a networking layer?
I separate HTTP transport from domain-facing application APIs.
In a production application I do not want every ViewModel creating
URLRequests, decoding JSON, interpreting HTTP status codes, and refreshing tokens.I normally put request execution and decoding behind an API client, centralize authentication behavior, and expose feature operations through repositories.
The ViewModel can then ask for something meaningful like
loadProjects()rather than knowing the endpoint path.This also gives me a natural dependency-injection boundary.
During testing I can replace the repository with a fake and verify screen behavior without making a real network request.
22. How do you handle API failures?
I classify errors based on what the application can do about them.
In one production flow, a generic error handler initially treated timeouts, validation failures, authentication failures, and server errors identically.
That made the UI less useful and also created risky retry behavior.
I separated transport errors from domain errors. A timeout could offer retry, an expired token could initiate authentication recovery, and a validation error could be shown directly to the user.
For write operations I also considered whether retrying was safe and whether the server supported idempotency.
The result was both clearer user feedback and safer failure handling.
23. How would you implement an offline-first feature?
Offline-first means local data becomes the source the UI observes while the network becomes a synchronization mechanism.
Imagine an inspection or project-management application where users may lose connectivity while editing.
I would save changes locally immediately and mark unsynchronized records as pending.
When connectivity becomes available, the synchronization layer can push pending changes and incorporate remote updates.
I would explicitly define conflict behavior rather than simply applying last-write-wins everywhere.
I would test offline editing, application termination, reconnection, duplicated requests, and conflicting changes.
The result should be that the user can continue working without thinking about network availability while the system still preserves data integrity.
24. When would you choose SwiftData versus Core Data?
For a new application with appropriate OS requirements and relatively straightforward persistence needs, SwiftData is worth evaluating first.
For a mature application, migration risk becomes part of the decision.
If I inherited an application with years of Core Data migrations and stable production data, I would not propose a rewrite solely because SwiftData has a newer API.
I would first evaluate what problem the migration actually solves, what capabilities we depend on, and how safely existing user data can be migrated.
If the business benefit is small and migration risk is high, keeping Core Data can be the better engineering decision.
I treat persistence changes as product-risk decisions, not framework-fashion decisions.
25. What should be stored in Keychain?
Keychain is intended for sensitive persistent data such as authentication credentials, tokens, and cryptographic material.
In an application with authenticated API access, I would store refresh credentials in Keychain but keep normal preferences such as selected theme or UI configuration in a regular preference store.
I would also define credential lifecycle clearly.
For example, logout should remove the relevant secure credentials rather than only changing the visible UI state.
That prevents a situation where the application appears logged out while sensitive authentication material is still available.
26. What is your iOS testing strategy?
I use different test levels for different kinds of risk.
In a subscription or purchase-related feature, I would put pricing and state-transition logic under fast unit tests.
Repository and API behavior can be covered at integration boundaries, while UI tests should focus on a smaller number of critical user journeys.
If a production bug is discovered, I add focused regression coverage around the actual failure mode.
This gives us fast CI feedback without depending on slow UI automation for every business rule.
The result is a test suite that gives useful engineering confidence rather than simply producing a large test count.
27. How do you test asynchronous code?
I try to control dependencies rather than relying on timing.
For example, suppose changing a filter starts a network request.
A real race condition can occur if the user selects option A and then B, but request A finishes after request B and overwrites the newer state.
In the test, I would use a fake repository where I control when each response completes.
I would deliberately complete B first and A second and verify that A can no longer update the visible state.
That directly proves the concurrency rule I care about and is much more reliable than inserting sleeps into the test.
28. How do you prevent regressions?
An important production bug should ideally leave behind a test.
Suppose users occasionally crash when returning to the application after it has been backgrounded during an asynchronous operation.
I would reproduce the lifecycle sequence, identify which object or state becomes invalid, and fix the underlying ownership problem.
Then I would add the narrowest automated test that captures the failure.
After deployment I would still monitor the same crash signature in production.
The regression test prevents us from reintroducing the known bug, while production monitoring verifies that the real-world issue actually disappeared.
29. How would you investigate a production crash?
I start with evidence: stack trace, app version, iOS version, device, breadcrumbs, logs, user flow, and recent releases.
In a production application I would first determine whether the crash clusters around a specific feature or lifecycle event.
For example, if most reports happen after returning from background, I would reproduce that transition while varying network state and the timing of asynchronous work.
Once I find the cause, I prefer fixing the underlying ownership or state issue rather than simply guarding the crashing line.
I would add regression coverage and release the change carefully.
Finally, I would monitor the same crash group after rollout.
In an earlier production environment, following this evidence-driven approach to crash investigation and fixes helped reduce the crash rate from about 4% to around 1%.
30. What questions would you ask the iOS team?
What are the biggest technical problems in the iOS codebase today?
How much of the application is SwiftUI versus UIKit?
Are you actively adopting Swift's strict concurrency model?
How do you measure crash-free sessions, startup performance, memory usage, and application responsiveness?
How are architecture decisions proposed and reviewed?
How do you decide when technical debt should be addressed?
What happens after an engineer ships a feature? Do they also own its production metrics and incidents?
How do iOS, backend, QA, design, and product work together?
What would you expect a senior iOS engineer to improve during their first three to six months?
SwiftUI
11. What are the biggest advantages of SwiftUI?
SwiftUI provides a declarative and state-driven UI model.
Instead of manually instructing views how to update, developers describe what the UI should look like for a given state.
Example:
In a settings screen implemented with UIKit, multiple callbacks manually updated labels, switches, and loading states. When implementing a similar feature in SwiftUI, I exposed a single screen state and derived the UI from it. That reduced synchronization code and made states such as loading, error, empty, and loaded explicit.
The benefit is usually not “less code” alone.
It is more predictable state management.
12. What is the difference between @State and @Binding?
@State means a view owns local state.
@Binding means the view receives writable access to state owned elsewhere.
Example:
A reusable toggle component should not own the application's notification preference. The parent owns that state, and the child receives a binding. That keeps one source of truth while still allowing the child component to modify the value.
13. What is state hoisting in SwiftUI?
State hoisting means moving state to the lowest common owner that actually needs to control it.
Example:
I had a reusable filter component that initially stored its own selected option. That worked until another view also needed to reset the filter. I moved ownership to the parent and passed the selected value and change action down. That removed duplicated state and made the component reusable.
14. What causes a SwiftUI view to update?
SwiftUI reevaluates the view's body when relevant observed state changes.
But an important interview point is that body evaluation does not automatically mean the entire visible interface is recreated.
SwiftUI compares the resulting view structure and updates what is necessary.
Example:
If a search query changes, body evaluation is expected. But if I perform expensive sorting or transformation directly inside
body, that work may happen much more often than intended. I would move expensive transformations into the model layer or cache derived state when appropriate.
15. How would you diagnose SwiftUI performance problems?
I would measure first.
Useful tools include Instruments, SwiftUI profiling tools, Time Profiler, Allocations, and Xcode debugging tools.
Example:
I had a large scrolling list where changing one control caused unnecessary work across many rows. I checked view identity, made model data stable and immutable, provided stable identifiers, and moved expensive calculations outside the row body. Then I re-profiled the interaction instead of assuming the optimization worked.
16. When would you still use UIKit?
UIKit remains important for existing applications, highly customized interfaces, mature components, and certain integrations.
A senior engineer should not treat SwiftUI and UIKit as competing religions.
Example:
If an application already contains a stable UIKit navigation architecture, I would not rewrite it purely because SwiftUI exists. I might introduce new SwiftUI screens incrementally using hosting controllers and keep the migration boundary explicit.
Architecture
17. How would you structure a modern iOS application?
There is no single correct architecture.
A practical structure may include:
UI → Presentation → Domain → Data
But the number of layers should reflect actual complexity.
Example:
For a feature with several API calls, persistence, and reusable business rules, I may separate View, ViewModel, use-case/domain logic, and repositories. For a simple settings screen reading a few local preferences, introducing five abstraction layers would add more complexity than value.
Architecture should reduce change cost, not maximize the number of folders.
18. What is MVVM?
MVVM separates view rendering from presentation logic.
The View renders state and forwards interactions.
The ViewModel transforms domain data into state usable by the interface.
Example:
In an older screen, the ViewController formatted API responses, handled networking, stored pagination state, and updated cells. I moved remote access into a repository and presentation state into a ViewModel. The ViewController then became responsible mainly for binding state and user interactions. That significantly improved testability.
19. What is Clean Architecture and when is it useful?
Clean Architecture attempts to keep business rules independent from UI frameworks and infrastructure.
But the important part is dependency direction.
Example:
If domain rules directly depend on Core Data entities or API response objects, changing persistence or networking can affect business logic. Mapping infrastructure models into domain models keeps those concerns separate. I use that separation where the domain is substantial enough to justify it.
20. How would you prevent a ViewModel from becoming too large?
A ViewModel becomes problematic when it owns unrelated responsibilities.
I would look for separable concerns such as:
network access, persistence, formatting, analytics, navigation, validation, and business workflows.
Example:
Rather than immediately splitting a 1,500-line ViewModel into arbitrary smaller ViewModels, I would first identify responsibilities. Network access may move behind repositories, business operations into use cases, navigation into a coordinator, and reusable formatting into dedicated components. The goal is meaningful boundaries, not simply smaller files.
Networking and Data
21. How would you design a networking layer?
I generally want networking code to clearly separate transport, request construction, decoding, authentication, and domain-specific repositories.
For example:
URLSessioncan handle transport while an API client builds requests and decodes responses. Authentication can be isolated so token refresh behavior does not leak into every feature. Repositories then expose operations meaningful to the domain rather than raw HTTP endpoints.
This also makes networking easier to replace in tests.
22. How do you handle API errors?
I avoid representing every failure as a generic “Something went wrong.”
Different failures need different product behavior.
For example:
A timeout may be retryable, an expired token may trigger authentication recovery, a validation error should normally be shown to the user, and a server error may be logged and retried depending on the operation.
For important write operations, idempotency also matters.
23. How would you implement offline-first behavior?
The UI should usually observe a local source of truth.
Remote synchronization updates that local data.
Example:
In an inspection application, I would store work items locally and render from the local database. User edits would be saved immediately and marked with synchronization state. A background synchronization process could upload pending changes when connectivity returns. Conflict resolution needs explicit rules rather than blindly replacing whichever version is older.
Offline-first is not simply “add caching.”
It changes the data model.
24. SwiftData or Core Data?
SwiftData provides a more Swift-native persistence API and integrates naturally with modern SwiftUI applications.
Core Data remains mature and widely used in established production applications.
A practical answer is:
For a new application with straightforward persistence requirements and an appropriate deployment target, I would evaluate SwiftData first. For an existing Core Data application with years of migrations and established infrastructure, rewriting persistence without a clear business reason would rarely be justified.
25. What belongs in Keychain?
Sensitive credentials such as authentication tokens, keys, or secrets that must be securely persisted.
Ordinary application preferences should not automatically go into Keychain.
For example:
Theme preference belongs in normal local settings. A refresh token belongs in Keychain.
Testing
26. What is your iOS testing strategy?
A healthy strategy normally contains multiple layers.
Example:
For checkout logic, I would unit-test price calculation and state transitions, integration-test the networking and repository boundary with controlled dependencies, and keep a smaller number of UI tests for critical flows such as login and purchase completion. I do not try to prove all business logic through UI automation because those tests are slower and harder to diagnose.
Modern projects may use both Swift Testing and XCTest, depending on the codebase and testing type.
27. How do you test asynchronous code?
With async-compatible tests, asynchronous operations can be awaited directly.
The larger architectural goal is deterministic control over dependencies.
Example:
Instead of making a real network request during the test, I inject a fake repository whose response I control. That allows me to test loading, success, failure, and cancellation without depending on network timing.
28. How do you prevent regressions?
A production bug should ideally leave behind a test.
Example:
If a crash occurred after returning from background while a request completed, I would first reproduce the lifecycle sequence, fix the ownership or state problem, and then add the narrowest automated test that captures the failure. After rollout, I would monitor crash reporting to confirm the production signal actually disappears.
Regression testing should be connected to real failures.
Production, Performance and Ownership
29. How would you investigate a production crash?
I start with evidence rather than guesses.
For example:
I would inspect the crash stack, application version, operating-system version, device distribution, recent releases, breadcrumbs, and relevant logs. If the crash appears after a specific navigation or lifecycle transition, I would recreate that sequence locally. Once I identify the root cause, I would create a focused fix and regression test, release gradually where possible, and monitor the same production metric afterward.
Tools such as crash reporting platforms, Xcode Organizer, MetricKit, logs, and analytics can all contribute.
30. What questions should you ask the interviewer?
The questions you ask can reveal your engineering maturity.
I would ask things such as:
What are the biggest technical challenges in the iOS codebase today?
How much of the application is SwiftUI versus UIKit?
How do you measure mobile reliability: crash-free sessions, startup time, ANRs equivalent metrics, performance traces, or product metrics?
How are architecture decisions made?
How does the team handle technical debt?
What does the testing pyramid look like?
How do mobile, backend, product, design, and QA work together?
What would success look like for this role after the first three to six months?
Those questions lead to much more useful conversations than asking only which Swift version the team uses.
Swift Concurrency Deserves Extra Preparation
For senior iOS roles in particular, I would spend significant preparation time on Swift Concurrency.
You should be comfortable discussing:
async/await, structured concurrency, cancellation, async let, task groups, actors, @MainActor, Sendable, isolation, race conditions, and the difference between concurrency and parallelism.
Do not memorize definitions only.
Practice scenarios.
For example:
A user rapidly changes the selected account while each selection starts a request.
What happens if request A finishes after request B?
The problem is not whether you remember the syntax for Task.
The real question is how you guarantee that stale work cannot overwrite newer state.
A possible solution is to cancel the previous task when the selection changes, verify cancellation inside long-running work, and ensure only the latest request can update the visible state.
That is the kind of reasoning interviewers want to hear.
Prepare for UIKit Even If the Job Mentions SwiftUI
Many candidates prepare only for modern SwiftUI code.
That can be risky.
Large production applications may contain:
UIViewController, UITableView, UICollectionView, delegates, data sources, Auto Layout, storyboards, programmatic UIKit, coordinators, Combine, Objective-C, or years of legacy code.
A company hiring a senior engineer may actually care more about whether you can safely evolve an existing application than whether you can build a perfect greenfield SwiftUI demo.
Be ready to explain incremental migration.
For example:
I would avoid rewriting a stable UIKit application all at once. I would introduce SwiftUI at feature boundaries where it creates clear value, define interoperability patterns, keep shared domain logic UI-independent, and migrate gradually while measuring regressions.
That is usually a stronger senior-level answer than:
I would rewrite everything in SwiftUI.
Know the iOS Application Lifecycle
You should understand what happens when the application:
launches,
moves between foreground and background,
loses active state,
receives a push notification,
is terminated,
restores state,
or performs allowed background work.
Many difficult production bugs are lifecycle bugs.
For example, a task that works perfectly while the application is foregrounded may fail when the user locks the device.
A media application may require a completely different lifecycle strategy than a normal CRUD application.
Interviewers often use these scenarios because they reveal whether your knowledge goes beyond UI development.
Prepare Memory Management Properly
ARC questions are still relevant.
You should be able to explain:
strong references,
weak references,
unowned references,
retain cycles,
closure capture lists,
delegate ownership,
task lifetimes,
and how to diagnose leaks.
Do not answer memory questions only theoretically.
Mention tools.
For example:
I would use Xcode's Memory Graph to inspect ownership relationships and Instruments Allocations or Leaks to determine whether repeated navigation continuously increases retained objects.
Being able to investigate memory problems is more valuable than memorizing ARC definitions.
Learn Instruments
For senior interviews, saying “I would optimize the code” is not enough.
Explain how you would know what to optimize.
Typical tools include:
Time Profiler,
Allocations,
Leaks,
Network profiling,
Energy diagnostics,
SwiftUI performance tools,
and Xcode Organizer metrics.
The principle is simple:
**measure → identify bottleneck → change → measure again**
Performance work without measurement often becomes guesswork.
Be Ready for App Store and Production Questions
Senior mobile engineering includes everything that happens after the code compiles.
You may be asked about:
code signing,
provisioning,
TestFlight,
App Store releases,
phased rollout,
feature flags,
crash monitoring,
analytics,
privacy permissions,
background modes,
push notifications,
in-app purchases,
StoreKit 2,
or release rollback strategies.
Production ownership is part of mobile engineering.
AI Coding Tools Are Becoming Part of the Interview
More engineering teams now use tools such as ChatGPT, Codex, Claude Code, GitHub Copilot, or internal AI agents.
A good answer is not:
I use AI to write all my code.
And it is also not:
I never use AI.
A stronger engineering workflow sounds more like:
I use AI to explore unfamiliar code, generate first-pass implementations, refactor repetitive code, create test cases, or compare possible approaches. But I review the diff, verify assumptions against the actual codebase and documentation, run the tests, check edge cases, and keep architecture and product decisions under human control.
An experienced engineer should also know when AI should not be trusted blindly.
Examples include security-sensitive logic, destructive database operations, authentication code, billing logic, concurrency-sensitive changes, and migrations.
How to Practice Before the Interview
Do not prepare only by reading.
Take one project you know well and prepare several strong engineering stories from it.
You should ideally have examples involving:
a production crash,
a performance problem,
an architectural decision,
a disagreement or trade-off,
a difficult asynchronous workflow,
a testing improvement,
a feature you owned end to end,
a requirement that changed,
and a technical decision you would make differently today.
The same project can support many different interview questions.
The interviewer does not need thirty unrelated stories.
They need evidence that you have actually solved engineering problems.
A Good Answer Is Usually Better Than a Long Answer
Technical interviews are not university exams.
A five-minute lecture about protocol witness tables is not automatically stronger than a concise explanation tied to a real engineering problem.
A useful pattern is:
Give the direct answer first.
Then explain a real example.
Then discuss the trade-off.
For example:
I normally use actors when multiple concurrent operations need safe access to mutable shared state. One example would be authentication-token refresh. Without coordination, several failed API calls can start multiple refreshes simultaneously. I can isolate the refresh state in an actor, allow other requests to await the same refresh, and avoid races. The trade-off is that actor boundaries introduce asynchronous access, so I would not introduce one where simple immutable state already solves the problem.
That answer demonstrates both knowledge and judgment.
What Senior iOS Engineers Are Expected to Show
At senior level, interviewers normally expect more than the ability to implement screens.
They want evidence that you can make decisions that remain reasonable six months later.
That includes understanding when not to rewrite something, how to introduce new architecture incrementally, how to identify risk, how to review other engineers' code, how to communicate technical decisions, and how to balance product delivery against engineering quality.
You should be able to say:
This is technically cleaner, but the migration risk is not justified right now.
Or:
I would ship the smaller version first, but I would keep this boundary because we already know this area will expand.
Engineering maturity often appears in those trade-offs.
Final Preparation Checklist
Before an iOS technical interview, I would make sure I can confidently discuss Swift fundamentals, ARC, Swift Concurrency, SwiftUI and UIKit interoperability, architecture, networking, persistence, testing, performance, production debugging, App Store delivery, and at least several real engineering stories.
But the most important preparation is not memorizing definitions.
It is learning to explain your decisions.
When answering a technical question, try to show:
what the problem was, why you chose a particular approach, how you implemented it, how you verified it, what happened afterward, and what trade-offs were involved.
That is what turns a technically correct answer into an engineering answer.
And that is usually what separates someone who knows iOS APIs from someone who can own an iOS product in production.